@vectojs/core 1.19.0 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8,13 +8,14 @@
8
8
 
9
9
 
10
10
 
11
- var _chunkL4SWVP2Hjs = require('./chunk-L4SWVP2H.js');
12
11
 
12
+ var _chunkGKSCJ6AFjs = require('./chunk-GKSCJ6AF.js');
13
13
 
14
14
 
15
15
 
16
16
 
17
- var _chunkIA3KW4CGjs = require('./chunk-IA3KW4CG.js');
17
+
18
+ var _chunkAGP4VLF4js = require('./chunk-AGP4VLF4.js');
18
19
 
19
20
  // src/tree/Scene.ts
20
21
  var _animation = require('@vectojs/animation'); _createStarExport(_animation);
@@ -29,7 +30,7 @@ var PARTICLE_OFFSET_ORIGIN_X = 4;
29
30
  var PARTICLE_OFFSET_ORIGIN_Y = 5;
30
31
  var PARTICLE_OFFSET_SIZE = 6;
31
32
  var PARTICLE_OFFSET_LIFE = 7;
32
- var ComputeParticleEntity = (_class = class extends _chunkIA3KW4CGjs.Entity {
33
+ var ComputeParticleEntity = (_class = class extends _chunkAGP4VLF4js.Entity {
33
34
 
34
35
 
35
36
 
@@ -1539,32 +1540,96 @@ var Scene = (_class7 = class _Scene {
1539
1540
  __init29() {this.renderMode = "always"}
1540
1541
  /** Cap on distinct recorded dirty reasons (see `recordDirtyReason`). */
1541
1542
  static __initStatic4() {this.MAX_DIRTY_REASONS = 200}
1542
- __init30() {this._dirtyTracking = false}
1543
- __init31() {this._dirtyReasons = /* @__PURE__ */ new Map()}
1544
- __init32() {this.dirty = true}
1543
+ __init30() {this._phaseTiming = false}
1544
+ __init31() {this._phaseTotals = /* @__PURE__ */ new Map()}
1545
+ /**
1546
+ * Start or stop per-phase render timing.
1547
+ *
1548
+ * Off by default, and the probes compile to a single boolean test when off:
1549
+ * these sit on the frame path, so the disabled cost has to be nothing. Enable,
1550
+ * run the scene, then read {@link renderPhases}.
1551
+ *
1552
+ * Exists because a frame total cannot tell you where the time went. The
1553
+ * markdown streaming benchmark put render at 85-99% of an append's cost, and
1554
+ * there was no way to decompose that number further — which is exactly the
1555
+ * position that led to two wrong optimisation guesses earlier
1556
+ * (`CodeBlock` reuse, hit-grid fusion), both of which measured as no change.
1557
+ */
1558
+ setPhaseTiming(enabled) {
1559
+ this._phaseTiming = enabled;
1560
+ if (!enabled) this.clearRenderPhases();
1561
+ }
1562
+ /** Whether per-phase render timing is being recorded. */
1563
+ get phaseTiming() {
1564
+ return this._phaseTiming;
1565
+ }
1566
+ /**
1567
+ * Accumulate one phase sample.
1568
+ *
1569
+ * Totals rather than a per-frame log: the question is always "which phase owns
1570
+ * the frame", and a log of thousands of samples answers it less directly while
1571
+ * costing far more memory. `maxMs` is kept because a phase that is cheap on
1572
+ * average but spikes is a different problem from one that is uniformly slow.
1573
+ */
1574
+ _recordPhase(phase, ms) {
1575
+ const existing = this._phaseTotals.get(phase);
1576
+ if (existing) {
1577
+ existing.totalMs += ms;
1578
+ existing.calls++;
1579
+ if (ms > existing.maxMs) existing.maxMs = ms;
1580
+ return;
1581
+ }
1582
+ this._phaseTotals.set(phase, { totalMs: ms, calls: 1, maxMs: ms });
1583
+ }
1584
+ /**
1585
+ * Recorded phase timings, most expensive first, with each phase's share of the
1586
+ * measured total.
1587
+ *
1588
+ * `share` is the number that matters: a phase at 4% cannot be worth optimising
1589
+ * however inefficient it looks in isolation.
1590
+ */
1591
+ get renderPhases() {
1592
+ const entries = [...this._phaseTotals.entries()];
1593
+ const denominator = entries.filter(([phase]) => phase !== "render").reduce((sum, [, v]) => sum + v.totalMs, 0);
1594
+ return entries.map(([phase, v]) => ({
1595
+ phase,
1596
+ totalMs: +v.totalMs.toFixed(3),
1597
+ calls: v.calls,
1598
+ avgMs: +(v.totalMs / Math.max(1, v.calls)).toFixed(4),
1599
+ maxMs: +v.maxMs.toFixed(3),
1600
+ share: phase === "render" ? null : +(100 * v.totalMs / Math.max(1e-9, denominator)).toFixed(1)
1601
+ })).sort((a, b) => b.totalMs - a.totalMs);
1602
+ }
1603
+ /** Drop recorded phase timings, keeping timing enabled. */
1604
+ clearRenderPhases() {
1605
+ this._phaseTotals.clear();
1606
+ }
1607
+ __init32() {this._dirtyTracking = false}
1608
+ __init33() {this._dirtyReasons = /* @__PURE__ */ new Map()}
1609
+ __init34() {this.dirty = true}
1545
1610
  /** Whether to throttle rendering to 2 FPS when the scene is static to save power. */
1546
- __init33() {this.autoThrottle = true}
1611
+ __init35() {this.autoThrottle = true}
1547
1612
  // --- Frame telemetry (read via `frameStats`) ---------------------------
1548
1613
  /** Wall-clock ms spent inside the last `render()` call. */
1549
- __init34() {this._lastFrameMs = 0}
1614
+ __init36() {this._lastFrameMs = 0}
1550
1615
  /** Rolling exponential average of rendered-frame intervals, in ms. */
1551
- __init35() {this._avgFrameIntervalMs = 0}
1616
+ __init37() {this._avgFrameIntervalMs = 0}
1552
1617
  /** dt (ms) handed to the last rendered frame. */
1553
- __init36() {this._lastDt = 0}
1618
+ __init38() {this._lastDt = 0}
1554
1619
  /** Count of frames actually rendered since the loop started. */
1555
- __init37() {this._renderedFrames = 0}
1620
+ __init39() {this._renderedFrames = 0}
1556
1621
  /** Count of rAF ticks skipped (idle / capped) since the loop started. */
1557
- __init38() {this._skippedFrames = 0}
1622
+ __init40() {this._skippedFrames = 0}
1558
1623
  /** `time` of the previous *rendered* frame, for interval measurement. */
1559
- __init39() {this._lastRenderTick = 0}
1624
+ __init41() {this._lastRenderTick = 0}
1560
1625
  /**
1561
1626
  * Frame-rate cap (power saving). `0` = uncapped (native refresh). When set,
1562
1627
  * the loop renders at most `maxFPS` times per second; animations still run,
1563
1628
  * just less often. See {@link SceneOptions.maxFPS}.
1564
1629
  */
1565
- __init40() {this.maxFPS = 60}
1630
+ __init42() {this.maxFPS = 60}
1566
1631
  /** Whether the OS prefers-reduced-motion setting auto-caps the loop. */
1567
- __init41() {this.respectReducedMotion = true}
1632
+ __init43() {this.respectReducedMotion = true}
1568
1633
  /**
1569
1634
  * Reading direction for accessibility tab/traversal order (`'ltr'` default,
1570
1635
  * `'rtl'`). Controls the inline sort within a visual row in
@@ -1580,15 +1645,15 @@ var Scene = (_class7 = class _Scene {
1580
1645
  this.a11yNeedsReorder = true;
1581
1646
  }
1582
1647
  }
1583
- __init42() {this._readingDirection = "ltr"}
1648
+ __init44() {this._readingDirection = "ltr"}
1584
1649
  /** Cached media-query list; `.matches` is read live each frame. */
1585
- __init43() {this.reducedMotionQuery = null}
1650
+ __init45() {this.reducedMotionQuery = null}
1586
1651
  /** Cached `(forced-colors: active)` query (Windows High Contrast etc.). A
1587
1652
  * canvas gets NO automatic forced-colors treatment from the browser (it's
1588
1653
  * opaque pixels), so components must read {@link forcedColors} and repaint
1589
1654
  * with system colors themselves; a change listener repaints idle scenes. */
1590
- __init44() {this.forcedColorsQuery = null}
1591
- __init45() {this.forcedColorsChangeHandler = null}
1655
+ __init46() {this.forcedColorsQuery = null}
1656
+ __init47() {this.forcedColorsChangeHandler = null}
1592
1657
  /** True when the OS asks for reduced motion and we respect it. Read by the animation drivers. */
1593
1658
  get prefersReducedMotion() {
1594
1659
  return this.respectReducedMotion && !!_optionalChain([this, 'access', _21 => _21.reducedMotionQuery, 'optionalAccess', _22 => _22.matches]);
@@ -1608,82 +1673,105 @@ var Scene = (_class7 = class _Scene {
1608
1673
  * Throttle interval (ms) for the a11y/automation shadow sync. `0` = every
1609
1674
  * frame. See {@link SceneOptions.a11ySyncInterval}.
1610
1675
  */
1611
- __init46() {this.a11ySyncInterval = 0}
1676
+ __init48() {this.a11ySyncInterval = 0}
1612
1677
  /** Timestamp of the last a11y sync, for throttling. */
1613
- __init47() {this.lastA11ySync = -Infinity}
1678
+ __init49() {this.lastA11ySync = -Infinity}
1614
1679
  /** True if we skipped an a11y sync during animation and need to sync when at rest. */
1615
- __init48() {this.a11yPendingSyncAfterAnimation = false}
1680
+ __init50() {this.a11yPendingSyncAfterAnimation = false}
1616
1681
  // A11y / Automation Layer. `null` in non-DOM (SSR/Node) environments — the
1617
1682
  // whole projection degrades to a no-op so the engine's logic stays usable
1618
1683
  // server-side (e.g. headless layout / vector export) without jsdom.
1619
1684
 
1620
- __init49() {this.a11yElements = /* @__PURE__ */ new Map()}
1685
+ __init51() {this.a11yElements = /* @__PURE__ */ new Map()}
1621
1686
  /** DOM nodes mirroring static text content, keyed by entity id. */
1622
- __init50() {this.contentElements = /* @__PURE__ */ new Map()}
1687
+ __init52() {this.contentElements = /* @__PURE__ */ new Map()}
1623
1688
  /** Pending cold font-calibration frame per projected grid entity. */
1624
- __init51() {this.contentGridCalibrationFrames = /* @__PURE__ */ new Map()}
1689
+ __init53() {this.contentGridCalibrationFrames = /* @__PURE__ */ new Map()}
1625
1690
  /** Detached, untransformed font probes used by the cold calibration pass. */
1626
- __init52() {this.contentGridCalibrationProbes = /* @__PURE__ */ new Map()}
1691
+ __init54() {this.contentGridCalibrationProbes = /* @__PURE__ */ new Map()}
1692
+ /**
1693
+ * Monotonic stamp identifying the conditions grid cells were calibrated under.
1694
+ *
1695
+ * Calibration measures the difference between the advance the canvas grid assigns
1696
+ * a cluster and the width the browser lays it out at, then writes a per-cell
1697
+ * `scaleX`. That result stays valid until the font or the page scale changes, and
1698
+ * it lives on the cell element — so a cell carrying this stamp needs no further
1699
+ * work.
1700
+ *
1701
+ * The scan that feeds calibration was O(cells) on every revision bump: for a
1702
+ * streaming code block it re-derived a measurement key for every cell in the
1703
+ * block each frame in order to produce only ~20 distinct keys, costing about
1704
+ * 2.5 ms/frame after the `style.font` fix and still over half of `a11ySync`. Since
1705
+ * carrier reuse (#244) leaves untouched lines — and therefore their calibrated
1706
+ * transforms — in place, cells stamped with the current generation can simply be
1707
+ * skipped, making the scan O(new cells) instead.
1708
+ *
1709
+ * A plain incrementing integer rather than the descriptive calibration key,
1710
+ * because it goes into an attribute selector and must not need escaping.
1711
+ */
1712
+ __init55() {this.contentGridCalibrationGeneration = 0}
1713
+ /** The `(fontEpoch, pageScale)` pair the current generation corresponds to. */
1714
+ __init56() {this.contentGridCalibrationStamp = ""}
1627
1715
  /** Invalidates grid font calibration after browser font availability changes. */
1628
- __init53() {this.contentFontEpoch = 0}
1716
+ __init57() {this.contentFontEpoch = 0}
1629
1717
  /** Cached Canvas-to-client scale for the current font/viewport epoch. */
1630
- __init54() {this.contentMetricScaleEpoch = -1}
1631
- __init55() {this.contentMetricScaleX = 1}
1632
- __init56() {this.contentProjectionEnabled = true}
1718
+ __init58() {this.contentMetricScaleEpoch = -1}
1719
+ __init59() {this.contentMetricScaleX = 1}
1720
+ __init60() {this.contentProjectionEnabled = true}
1633
1721
  // Virtualization margin (px) for content projection; `undefined` → one
1634
1722
  // viewport height, resolved at sync time. `Infinity` = materialize everything.
1635
- __init57() {this.contentProjectionMargin = void 0}
1723
+ __init61() {this.contentProjectionMargin = void 0}
1636
1724
  /**
1637
1725
  * True while a text-selection drag that started on a projection's blank
1638
1726
  * region (no text node under the press) is being driven manually — the
1639
1727
  * browser has no native anchor for it, so mousemove extends the Selection
1640
1728
  * from the position we resolved ourselves.
1641
1729
  */
1642
- __init58() {this.blankRegionSelectionDrag = false}
1643
- __init59() {this.contentSelectionAnchor = null}
1644
- __init60() {this.contentSelectionEndListener = null}
1730
+ __init62() {this.blankRegionSelectionDrag = false}
1731
+ __init63() {this.contentSelectionAnchor = null}
1732
+ __init64() {this.contentSelectionEndListener = null}
1645
1733
  // Animation/interactive flags collected during the render walk (tree-walk
1646
1734
  // fusion): the loop reads last frame's answers instead of re-walking the
1647
1735
  // tree up to 4× per tick. Start true so the first tick stays conservative.
1648
- __init61() {this.frameHadAnimation = true}
1649
- __init62() {this.frameHadInteractive = true}
1736
+ __init65() {this.frameHadAnimation = true}
1737
+ __init66() {this.frameHadInteractive = true}
1650
1738
 
1651
1739
  /** Active `(resolution: Ndppx)` media query watching for a runtime DPR change
1652
1740
  * (window moved between monitors, browser zoom) so the canvas backing store
1653
1741
  * can be re-scaled — otherwise it stays rasterized at the old DPR and blurs.
1654
1742
  * A resolution media query only fires when leaving its exact value, so the
1655
1743
  * handler re-arms a fresh query for the new DPR each time. */
1656
- __init63() {this.dprMediaQuery = null}
1744
+ __init67() {this.dprMediaQuery = null}
1657
1745
  /** For embedded (`disableWindowResize`) scenes: observes the canvas element so
1658
1746
  * a CSS/layout-driven size change re-runs `resize()`. A window `resize`
1659
1747
  * listener never fires for these (the window isn't what changed), so without
1660
1748
  * this an embedded canvas stayed at its initial size forever. */
1661
- __init64() {this.canvasResizeObserver = null}
1662
- __init65() {this.dprChangeHandler = null}
1663
- __init66() {this.focusedA11yElement = null}
1749
+ __init68() {this.canvasResizeObserver = null}
1750
+ __init69() {this.dprChangeHandler = null}
1751
+ __init70() {this.focusedA11yElement = null}
1664
1752
  /** Last geometry `syncOverlayGeometry` wrote, so an unchanged frame can skip the
1665
1753
  * style writes entirely. Reset to `null` to force the next sync (a new overlay
1666
1754
  * layer was created and has never been positioned). */
1667
- __init67() {this._overlayGeometry = null}
1755
+ __init71() {this._overlayGeometry = null}
1668
1756
  /** Shadow elements the pointer is currently inside. Lets a removal that happens
1669
1757
  * mid-hover synthesize the `pointerleave` the browser never sends for a
1670
1758
  * detached element, so the entity doesn't keep its hover state. */
1671
- __init68() {this.hoveredA11yElements = /* @__PURE__ */ new WeakSet()}
1759
+ __init72() {this.hoveredA11yElements = /* @__PURE__ */ new WeakSet()}
1672
1760
  /** Persistent tabindex=-1 element in a11yRoot. When the focused a11y mirror is
1673
1761
  * pruned (virtualization/streaming/removal) while it holds focus, we move
1674
1762
  * focus here instead of letting the browser drop it to <body> — keeping the
1675
1763
  * screen-reader virtual cursor inside the scene's a11y region. */
1676
- __init69() {this.focusSentinel = null}
1677
- __init70() {this.caretBlinkTimer = null}
1678
- __init71() {this.a11yNeedsReorder = true}
1679
- __init72() {this.portalRoot = null}
1680
- __init73() {this.fullViewportElements = []}
1681
- __init74() {this.normalElements = []}
1682
- __init75() {this.activeIds = /* @__PURE__ */ new Set()}
1683
- __init76() {this.activePortalsThisFrame = /* @__PURE__ */ new Set()}
1684
- __init77() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
1685
- __init78() {this.portalEntities = /* @__PURE__ */ new Map()}
1686
- __init79() {this.renderOrderCounter = 0}
1764
+ __init73() {this.focusSentinel = null}
1765
+ __init74() {this.caretBlinkTimer = null}
1766
+ __init75() {this.a11yNeedsReorder = true}
1767
+ __init76() {this.portalRoot = null}
1768
+ __init77() {this.fullViewportElements = []}
1769
+ __init78() {this.normalElements = []}
1770
+ __init79() {this.activeIds = /* @__PURE__ */ new Set()}
1771
+ __init80() {this.activePortalsThisFrame = /* @__PURE__ */ new Set()}
1772
+ __init81() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
1773
+ __init82() {this.portalEntities = /* @__PURE__ */ new Map()}
1774
+ __init83() {this.renderOrderCounter = 0}
1687
1775
  /**
1688
1776
  * Monotonic render-frame counter, bumped once per authoritative `render()`
1689
1777
  * pass. Entities stamp their per-frame world-matrix cache with this value and
@@ -1692,7 +1780,7 @@ var Scene = (_class7 = class _Scene {
1692
1780
  * back to the ancestor walk. Public for the same reason `Entity._getTrig`/
1693
1781
  * `_setWorldCache` are: it is a cross-class render-internal contract.
1694
1782
  */
1695
- __init80() {this.currentFrame = 0}
1783
+ __init84() {this.currentFrame = 0}
1696
1784
  // ── WASM transform backend (invisible accelerator) ──────────────────────────
1697
1785
  // When `_transformBackend === 'wasm'`, the main render walk sources each
1698
1786
  // entity's world matrix from an SoA store composed by `_wasm` (see
@@ -1700,24 +1788,24 @@ var Scene = (_class7 = class _Scene {
1700
1788
  // fallback and the default: a null backend, a non-main renderer, or any entity
1701
1789
  // absent from the store all fall back to the JS composition, so WASM can only
1702
1790
  // ever change *how fast* a world matrix is produced, never *what* it is.
1703
- __init81() {this._wasm = null}
1704
- __init82() {this._transformBackend = "js"}
1791
+ __init85() {this._wasm = null}
1792
+ __init86() {this._transformBackend = "js"}
1705
1793
  // Resident store state (Stage 3). The store layout — slot assignment + sibling
1706
1794
  // runs — depends only on tree TOPOLOGY, so it is rebuilt only when the
1707
1795
  // structure changes (add/remove/reparent bump `_structureVersion`). Between
1708
1796
  // rebuilds the per-frame cost is: gather each entity's transform into the
1709
1797
  // resident wasm input view + run the kernel — no reallocation, no readback.
1710
- __init83() {this._treeStore = null}
1711
- __init84() {this._slotEntity = []}
1798
+ __init87() {this._treeStore = null}
1799
+ __init88() {this._slotEntity = []}
1712
1800
  // store slot -> entity (also validates slots)
1713
- __init85() {this._wasmInputs = null}
1714
- __init86() {this._wasmWorld = null}
1715
- __init87() {this._structureVersion = 0}
1716
- __init88() {this._storeStructureVersion = -1}
1801
+ __init89() {this._wasmInputs = null}
1802
+ __init90() {this._wasmWorld = null}
1803
+ __init91() {this._structureVersion = 0}
1804
+ __init92() {this._storeStructureVersion = -1}
1717
1805
  // Cached list of ComputeParticleEntity instances in the tree, keyed by the
1718
1806
  // structure version it was gathered at. Rebuilt only on a topology change.
1719
- __init89() {this._computeEntities = []}
1720
- __init90() {this._computeEntitiesVersion = -1}
1807
+ __init93() {this._computeEntities = []}
1808
+ __init94() {this._computeEntitiesVersion = -1}
1721
1809
  /** Invalidate the resident WASM store layout; the next wasm-mode frame rebuilds
1722
1810
  * it. Called by `Entity.add`/`remove` (topology changes only). */
1723
1811
  markStructureChanged() {
@@ -1799,7 +1887,7 @@ var Scene = (_class7 = class _Scene {
1799
1887
  * cached globally; the instance is per-Scene, which is the isolation that
1800
1888
  * actually matters.
1801
1889
  */
1802
- __init91() {this._wasmRuntime = null}
1890
+ __init95() {this._wasmRuntime = null}
1803
1891
  /**
1804
1892
  * Load (or reuse) this Scene's shared WASM runtime.
1805
1893
  *
@@ -1827,30 +1915,30 @@ var Scene = (_class7 = class _Scene {
1827
1915
  get wasmRuntime() {
1828
1916
  return this._wasmRuntime;
1829
1917
  }
1830
- __init92() {this._hitWasm = null}
1918
+ __init96() {this._hitWasm = null}
1831
1919
  // Cache key: which frame + structure version the grid was last (successfully,
1832
1920
  // non-overflowing) built for. findEntityAt is called ad-hoc (pointer
1833
1921
  // hover/click), not every frame, so the grid is refreshed lazily on demand
1834
1922
  // rather than proactively every render() — unlike the transform store, which
1835
1923
  // every frame's draw depends on.
1836
- __init93() {this._hitGridFrame = -1}
1837
- __init94() {this._hitGridOk = false}
1838
- __init95() {this._hitSlotEntity = []}
1839
- __init96() {this._hitBoundless = []}
1924
+ __init97() {this._hitGridFrame = -1}
1925
+ __init98() {this._hitGridOk = false}
1926
+ __init99() {this._hitSlotEntity = []}
1927
+ __init100() {this._hitBoundless = []}
1840
1928
  /** Reused buffer for the fused gather, so a pointer query allocates nothing. */
1841
- __init97() {this._hitGatherBuffer = null}
1929
+ __init101() {this._hitGatherBuffer = null}
1842
1930
  /**
1843
1931
  * Whether the last grid build sourced its AABBs from the WASM transform store
1844
1932
  * rather than recomputing them in JS. Diagnostic only — both paths must
1845
1933
  * produce the same entity for a given point.
1846
1934
  */
1847
- __init98() {this._hitFusedGather = false}
1935
+ __init102() {this._hitFusedGather = false}
1848
1936
  /**
1849
1937
  * Whether `compute_aabbs` has run against the current frame's world matrices.
1850
1938
  * The AABB pass is only meaningful after a `compose_*`, so the fused gather
1851
1939
  * must not read the views before then.
1852
1940
  */
1853
- __init99() {this._wasmAabbsFresh = false}
1941
+ __init103() {this._wasmAabbsFresh = false}
1854
1942
  /** Did the last hit-grid build use the fused (WASM-store) gather? */
1855
1943
  get hitGatherPath() {
1856
1944
  return this._hitFusedGather ? "fused" : "js";
@@ -1964,25 +2052,25 @@ var Scene = (_class7 = class _Scene {
1964
2052
  // EasingFn (which cannot cross into WASM) all fall through to it — WASM can
1965
2053
  // only ever change *how* a driver is advanced, never *what* value it lands
1966
2054
  // on.
1967
- __init100() {this._animWasm = null}
2055
+ __init104() {this._animWasm = null}
1968
2056
  // Entities with at least one active driver, added by Entity._spawnDriver.
1969
2057
  // Self-pruning: _tickBatchedDrivers drops an entry the first time it visits
1970
2058
  // an entity whose drivers have since all completed or been removed. This is
1971
2059
  // what lets the batch pass find its candidates in O(active drivers), not
1972
2060
  // O(tree size) — the exact mistake G3's first integrated benchmark made.
1973
- __init101() {this._activeDriverEntities = /* @__PURE__ */ new Set()}
2061
+ __init105() {this._activeDriverEntities = /* @__PURE__ */ new Set()}
1974
2062
  // Reused across frames instead of allocating a fresh array + N {entity,prop,
1975
2063
  // driver} objects every call — the integrated benchmark
1976
2064
  // (benchmarks/anim-wasm-scene) found that allocation churn was the
1977
2065
  // dominant integrated cost, not the wasm kernel itself. Parallel arrays,
1978
2066
  // truncated to the live count after each use so a stale tail slot never
1979
2067
  // pins a no-longer-active entity/driver in memory.
1980
- __init102() {this._springEntities = []}
1981
- __init103() {this._springProps = []}
1982
- __init104() {this._springDrivers = []}
1983
- __init105() {this._tweenEntities = []}
1984
- __init106() {this._tweenProps = []}
1985
- __init107() {this._tweenDrivers = []}
2068
+ __init106() {this._springEntities = []}
2069
+ __init107() {this._springProps = []}
2070
+ __init108() {this._springDrivers = []}
2071
+ __init109() {this._tweenEntities = []}
2072
+ __init110() {this._tweenProps = []}
2073
+ __init111() {this._tweenDrivers = []}
1986
2074
  /**
1987
2075
  * Minimum number of batchable (spring, or named-easing tween) active drivers
1988
2076
  * before a frame engages the WASM batch path at all; below it, every driver
@@ -2054,7 +2142,7 @@ var Scene = (_class7 = class _Scene {
2054
2142
  * Setting {@link animDriverGateCount} overwrites all three, so existing code
2055
2143
  * that tuned the single knob keeps working unchanged.
2056
2144
  */
2057
- __init108() {this._animBatchedLastFrame = false}
2145
+ __init112() {this._animBatchedLastFrame = false}
2058
2146
  /**
2059
2147
  * Whether the WASM batch path actually ran on the most recent frame.
2060
2148
  *
@@ -2066,7 +2154,7 @@ var Scene = (_class7 = class _Scene {
2066
2154
  get animBatchedLastFrame() {
2067
2155
  return this._animBatchedLastFrame;
2068
2156
  }
2069
- __init109() {this.animGate = {
2157
+ __init113() {this.animGate = {
2070
2158
  spring: 128,
2071
2159
  tween: 256,
2072
2160
  mixed: 128
@@ -2104,7 +2192,7 @@ var Scene = (_class7 = class _Scene {
2104
2192
  // (benchmarks/particle-wasm). f32 (matches the WGSL shader), bit-identical to
2105
2193
  // a JS f32 reference oracle; updateCPU (f64) stays the permanent fallback when
2106
2194
  // no backend is installed or a scene runs on WebGPU.
2107
- __init110() {this._particleWasm = null}
2195
+ __init114() {this._particleWasm = null}
2108
2196
  /** Which backend runs the CPU particle simulation. Reflects only whether a
2109
2197
  * backend is installed (the WebGPU compute path, when active, is used first
2110
2198
  * regardless). */
@@ -2354,46 +2442,46 @@ var Scene = (_class7 = class _Scene {
2354
2442
  * sync, so retaining the order prevents a newly opened overlay from spending
2355
2443
  * its first frame below previously projected controls.
2356
2444
  */
2357
- __init111() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
2445
+ __init115() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
2358
2446
  // Optional WebGL point-cloud layer (see SceneOptions.pointBackend).
2359
- __init112() {this.pointRenderer = null}
2360
- __init113() {this.glCanvas = null}
2361
- __init114() {this.glContextLostHandler = null}
2362
- __init115() {this.glContextRestoredHandler = null}
2447
+ __init116() {this.pointRenderer = null}
2448
+ __init117() {this.glCanvas = null}
2449
+ __init118() {this.glContextLostHandler = null}
2450
+ __init119() {this.glContextRestoredHandler = null}
2363
2451
 
2364
2452
 
2365
2453
 
2366
- __init116() {this.disableWindowResize = false}
2454
+ __init120() {this.disableWindowResize = false}
2367
2455
  /** See {@link SceneOptions.maxDPR}. `undefined` = uncapped (real DPR). */
2368
2456
 
2369
2457
  // WebGPU properties
2370
- __init117() {this.destroyed = false}
2371
- __init118() {this.device = null}
2372
- __init119() {this.deviceLost = false}
2373
- __init120() {this.particleBackend = "auto"}
2374
- __init121() {this._webgpuDisabled = false}
2458
+ __init121() {this.destroyed = false}
2459
+ __init122() {this.device = null}
2460
+ __init123() {this.deviceLost = false}
2461
+ __init124() {this.particleBackend = "auto"}
2462
+ __init125() {this._webgpuDisabled = false}
2375
2463
  get webgpuDisabled() {
2376
2464
  return this._webgpuDisabled || this.particleBackend === "cpu";
2377
2465
  }
2378
2466
  set webgpuDisabled(value) {
2379
2467
  this._webgpuDisabled = value;
2380
2468
  }
2381
- __init122() {this.recoveryTimerId = null}
2382
- __init123() {this.manager = null}
2383
- __init124() {this.initializingWebGPU = false}
2384
- __init125() {this.gpuCanvas = null}
2385
- __init126() {this.gpuContext = null}
2469
+ __init126() {this.recoveryTimerId = null}
2470
+ __init127() {this.manager = null}
2471
+ __init128() {this.initializingWebGPU = false}
2472
+ __init129() {this.gpuCanvas = null}
2473
+ __init130() {this.gpuContext = null}
2386
2474
  /** True while the GPU canvas holds a presented particle frame (needs clearing when they leave). */
2387
- __init127() {this.gpuHasContent = false}
2388
- __init128() {this.mouseX = -9999}
2389
- __init129() {this.mouseY = -9999}
2390
- __init130() {this.pointerMoveListener = null}
2391
- __init131() {this.pointerLeaveListener = null}
2475
+ __init131() {this.gpuHasContent = false}
2476
+ __init132() {this.mouseX = -9999}
2477
+ __init133() {this.mouseY = -9999}
2478
+ __init134() {this.pointerMoveListener = null}
2479
+ __init135() {this.pointerLeaveListener = null}
2392
2480
  /** Element the pointer listeners are bound to (parent container if present,
2393
2481
  * else the canvas). Stored so `destroy()` detaches from the same element. */
2394
- __init132() {this.pointerEventTarget = null}
2395
- __init133() {this.hasWarnedZeroSize = false}
2396
- __init134() {this.fontLoadHandler = null}
2482
+ __init136() {this.pointerEventTarget = null}
2483
+ __init137() {this.hasWarnedZeroSize = false}
2484
+ __init138() {this.fontLoadHandler = null}
2397
2485
  // ── Dev-mode warning infrastructure ──────────────────────────────
2398
2486
  //
2399
2487
  // Enable with `Scene.devMode = true` or by setting `globalThis.__DEV__`.
@@ -2411,7 +2499,7 @@ var Scene = (_class7 = class _Scene {
2411
2499
  return false;
2412
2500
  }
2413
2501
 
2414
- __init135() {this._devFrameCount = 0}
2502
+ __init139() {this._devFrameCount = 0}
2415
2503
  _devWarn(message) {
2416
2504
  if (!this._devActive) return;
2417
2505
  console.warn(`[vectojs/dev] ${message}`);
@@ -2455,7 +2543,7 @@ var Scene = (_class7 = class _Scene {
2455
2543
  };
2456
2544
  walkProjections(this.root);
2457
2545
  }
2458
- constructor(canvas, options = {}) {;_class7.prototype.__init25.call(this);_class7.prototype.__init26.call(this);_class7.prototype.__init27.call(this);_class7.prototype.__init28.call(this);_class7.prototype.__init29.call(this);_class7.prototype.__init30.call(this);_class7.prototype.__init31.call(this);_class7.prototype.__init32.call(this);_class7.prototype.__init33.call(this);_class7.prototype.__init34.call(this);_class7.prototype.__init35.call(this);_class7.prototype.__init36.call(this);_class7.prototype.__init37.call(this);_class7.prototype.__init38.call(this);_class7.prototype.__init39.call(this);_class7.prototype.__init40.call(this);_class7.prototype.__init41.call(this);_class7.prototype.__init42.call(this);_class7.prototype.__init43.call(this);_class7.prototype.__init44.call(this);_class7.prototype.__init45.call(this);_class7.prototype.__init46.call(this);_class7.prototype.__init47.call(this);_class7.prototype.__init48.call(this);_class7.prototype.__init49.call(this);_class7.prototype.__init50.call(this);_class7.prototype.__init51.call(this);_class7.prototype.__init52.call(this);_class7.prototype.__init53.call(this);_class7.prototype.__init54.call(this);_class7.prototype.__init55.call(this);_class7.prototype.__init56.call(this);_class7.prototype.__init57.call(this);_class7.prototype.__init58.call(this);_class7.prototype.__init59.call(this);_class7.prototype.__init60.call(this);_class7.prototype.__init61.call(this);_class7.prototype.__init62.call(this);_class7.prototype.__init63.call(this);_class7.prototype.__init64.call(this);_class7.prototype.__init65.call(this);_class7.prototype.__init66.call(this);_class7.prototype.__init67.call(this);_class7.prototype.__init68.call(this);_class7.prototype.__init69.call(this);_class7.prototype.__init70.call(this);_class7.prototype.__init71.call(this);_class7.prototype.__init72.call(this);_class7.prototype.__init73.call(this);_class7.prototype.__init74.call(this);_class7.prototype.__init75.call(this);_class7.prototype.__init76.call(this);_class7.prototype.__init77.call(this);_class7.prototype.__init78.call(this);_class7.prototype.__init79.call(this);_class7.prototype.__init80.call(this);_class7.prototype.__init81.call(this);_class7.prototype.__init82.call(this);_class7.prototype.__init83.call(this);_class7.prototype.__init84.call(this);_class7.prototype.__init85.call(this);_class7.prototype.__init86.call(this);_class7.prototype.__init87.call(this);_class7.prototype.__init88.call(this);_class7.prototype.__init89.call(this);_class7.prototype.__init90.call(this);_class7.prototype.__init91.call(this);_class7.prototype.__init92.call(this);_class7.prototype.__init93.call(this);_class7.prototype.__init94.call(this);_class7.prototype.__init95.call(this);_class7.prototype.__init96.call(this);_class7.prototype.__init97.call(this);_class7.prototype.__init98.call(this);_class7.prototype.__init99.call(this);_class7.prototype.__init100.call(this);_class7.prototype.__init101.call(this);_class7.prototype.__init102.call(this);_class7.prototype.__init103.call(this);_class7.prototype.__init104.call(this);_class7.prototype.__init105.call(this);_class7.prototype.__init106.call(this);_class7.prototype.__init107.call(this);_class7.prototype.__init108.call(this);_class7.prototype.__init109.call(this);_class7.prototype.__init110.call(this);_class7.prototype.__init111.call(this);_class7.prototype.__init112.call(this);_class7.prototype.__init113.call(this);_class7.prototype.__init114.call(this);_class7.prototype.__init115.call(this);_class7.prototype.__init116.call(this);_class7.prototype.__init117.call(this);_class7.prototype.__init118.call(this);_class7.prototype.__init119.call(this);_class7.prototype.__init120.call(this);_class7.prototype.__init121.call(this);_class7.prototype.__init122.call(this);_class7.prototype.__init123.call(this);_class7.prototype.__init124.call(this);_class7.prototype.__init125.call(this);_class7.prototype.__init126.call(this);_class7.prototype.__init127.call(this);_class7.prototype.__init128.call(this);_class7.prototype.__init129.call(this);_class7.prototype.__init130.call(this);_class7.prototype.__init131.call(this);_class7.prototype.__init132.call(this);_class7.prototype.__init133.call(this);_class7.prototype.__init134.call(this);_class7.prototype.__init135.call(this);
2546
+ constructor(canvas, options = {}) {;_class7.prototype.__init25.call(this);_class7.prototype.__init26.call(this);_class7.prototype.__init27.call(this);_class7.prototype.__init28.call(this);_class7.prototype.__init29.call(this);_class7.prototype.__init30.call(this);_class7.prototype.__init31.call(this);_class7.prototype.__init32.call(this);_class7.prototype.__init33.call(this);_class7.prototype.__init34.call(this);_class7.prototype.__init35.call(this);_class7.prototype.__init36.call(this);_class7.prototype.__init37.call(this);_class7.prototype.__init38.call(this);_class7.prototype.__init39.call(this);_class7.prototype.__init40.call(this);_class7.prototype.__init41.call(this);_class7.prototype.__init42.call(this);_class7.prototype.__init43.call(this);_class7.prototype.__init44.call(this);_class7.prototype.__init45.call(this);_class7.prototype.__init46.call(this);_class7.prototype.__init47.call(this);_class7.prototype.__init48.call(this);_class7.prototype.__init49.call(this);_class7.prototype.__init50.call(this);_class7.prototype.__init51.call(this);_class7.prototype.__init52.call(this);_class7.prototype.__init53.call(this);_class7.prototype.__init54.call(this);_class7.prototype.__init55.call(this);_class7.prototype.__init56.call(this);_class7.prototype.__init57.call(this);_class7.prototype.__init58.call(this);_class7.prototype.__init59.call(this);_class7.prototype.__init60.call(this);_class7.prototype.__init61.call(this);_class7.prototype.__init62.call(this);_class7.prototype.__init63.call(this);_class7.prototype.__init64.call(this);_class7.prototype.__init65.call(this);_class7.prototype.__init66.call(this);_class7.prototype.__init67.call(this);_class7.prototype.__init68.call(this);_class7.prototype.__init69.call(this);_class7.prototype.__init70.call(this);_class7.prototype.__init71.call(this);_class7.prototype.__init72.call(this);_class7.prototype.__init73.call(this);_class7.prototype.__init74.call(this);_class7.prototype.__init75.call(this);_class7.prototype.__init76.call(this);_class7.prototype.__init77.call(this);_class7.prototype.__init78.call(this);_class7.prototype.__init79.call(this);_class7.prototype.__init80.call(this);_class7.prototype.__init81.call(this);_class7.prototype.__init82.call(this);_class7.prototype.__init83.call(this);_class7.prototype.__init84.call(this);_class7.prototype.__init85.call(this);_class7.prototype.__init86.call(this);_class7.prototype.__init87.call(this);_class7.prototype.__init88.call(this);_class7.prototype.__init89.call(this);_class7.prototype.__init90.call(this);_class7.prototype.__init91.call(this);_class7.prototype.__init92.call(this);_class7.prototype.__init93.call(this);_class7.prototype.__init94.call(this);_class7.prototype.__init95.call(this);_class7.prototype.__init96.call(this);_class7.prototype.__init97.call(this);_class7.prototype.__init98.call(this);_class7.prototype.__init99.call(this);_class7.prototype.__init100.call(this);_class7.prototype.__init101.call(this);_class7.prototype.__init102.call(this);_class7.prototype.__init103.call(this);_class7.prototype.__init104.call(this);_class7.prototype.__init105.call(this);_class7.prototype.__init106.call(this);_class7.prototype.__init107.call(this);_class7.prototype.__init108.call(this);_class7.prototype.__init109.call(this);_class7.prototype.__init110.call(this);_class7.prototype.__init111.call(this);_class7.prototype.__init112.call(this);_class7.prototype.__init113.call(this);_class7.prototype.__init114.call(this);_class7.prototype.__init115.call(this);_class7.prototype.__init116.call(this);_class7.prototype.__init117.call(this);_class7.prototype.__init118.call(this);_class7.prototype.__init119.call(this);_class7.prototype.__init120.call(this);_class7.prototype.__init121.call(this);_class7.prototype.__init122.call(this);_class7.prototype.__init123.call(this);_class7.prototype.__init124.call(this);_class7.prototype.__init125.call(this);_class7.prototype.__init126.call(this);_class7.prototype.__init127.call(this);_class7.prototype.__init128.call(this);_class7.prototype.__init129.call(this);_class7.prototype.__init130.call(this);_class7.prototype.__init131.call(this);_class7.prototype.__init132.call(this);_class7.prototype.__init133.call(this);_class7.prototype.__init134.call(this);_class7.prototype.__init135.call(this);_class7.prototype.__init136.call(this);_class7.prototype.__init137.call(this);_class7.prototype.__init138.call(this);_class7.prototype.__init139.call(this);
2459
2547
  this.canvas = canvas;
2460
2548
  this.debugA11y = _nullishCoalesce(options.debugA11y, () => ( false));
2461
2549
  this.disableWindowResize = _nullishCoalesce(options.disableWindowResize, () => ( false));
@@ -2486,7 +2574,7 @@ var Scene = (_class7 = class _Scene {
2486
2574
  this.forcedColorsChangeHandler = () => this.markDirty();
2487
2575
  _optionalChain([this, 'access', _45 => _45.forcedColorsQuery, 'access', _46 => _46.addEventListener, 'optionalCall', _47 => _47("change", this.forcedColorsChangeHandler)]);
2488
2576
  }
2489
- this.root = new class RootEntity extends _chunkIA3KW4CGjs.Entity {
2577
+ this.root = new class RootEntity extends _chunkAGP4VLF4js.Entity {
2490
2578
  isPointInside() {
2491
2579
  return false;
2492
2580
  }
@@ -2495,7 +2583,7 @@ var Scene = (_class7 = class _Scene {
2495
2583
  }
2496
2584
  }("root");
2497
2585
  this.root._scene = this;
2498
- this.overlayRoot = new class OverlayRoot extends _chunkIA3KW4CGjs.Entity {
2586
+ this.overlayRoot = new class OverlayRoot extends _chunkAGP4VLF4js.Entity {
2499
2587
  isPointInside() {
2500
2588
  return false;
2501
2589
  }
@@ -2506,7 +2594,7 @@ var Scene = (_class7 = class _Scene {
2506
2594
  if (options.renderer) {
2507
2595
  this.renderer = options.renderer;
2508
2596
  } else {
2509
- this.renderer = new (0, _chunkL4SWVP2Hjs.CanvasRenderer)(
2597
+ this.renderer = new (0, _chunkGKSCJ6AFjs.CanvasRenderer)(
2510
2598
  canvas,
2511
2599
  this.disableWindowResize ? { width: this.width, height: this.height } : void 0,
2512
2600
  this.maxDPR
@@ -2732,12 +2820,35 @@ var Scene = (_class7 = class _Scene {
2732
2820
  this.contentSelectionAnchor = null;
2733
2821
  if (this.a11yRoot) this.a11yRoot.style.pointerEvents = "none";
2734
2822
  }
2823
+ /**
2824
+ * Index of the carrier line currently holding a selection inside `el`, or
2825
+ * `null`.
2826
+ *
2827
+ * Lets a partial re-materialization decide whether the user's selection is even
2828
+ * affected. Checks the tracked anchor first (it survives a drag) and falls back
2829
+ * to the live DOM selection.
2830
+ */
2831
+ contentGridSelectionLine(el) {
2832
+ const candidates = [_optionalChain([this, 'access', _62 => _62.contentSelectionAnchor, 'optionalAccess', _63 => _63.node])];
2833
+ if (typeof window !== "undefined" && typeof window.getSelection === "function") {
2834
+ const selection = window.getSelection();
2835
+ candidates.push(_optionalChain([selection, 'optionalAccess', _64 => _64.anchorNode]), _optionalChain([selection, 'optionalAccess', _65 => _65.focusNode]));
2836
+ }
2837
+ for (const candidate of candidates) {
2838
+ if (!candidate || !el.contains(candidate)) continue;
2839
+ let cursor = candidate;
2840
+ while (cursor && cursor.parentNode !== el) cursor = cursor.parentNode;
2841
+ const lineIndex = _optionalChain([cursor, 'optionalAccess', _66 => _66.dataset, 'optionalAccess', _67 => _67.vectoGridLine]);
2842
+ if (lineIndex !== void 0) return Number(lineIndex);
2843
+ }
2844
+ return null;
2845
+ }
2735
2846
  releaseContentSelectionForRebuild(el) {
2736
2847
  const selection = typeof window !== "undefined" && typeof window.getSelection === "function" ? window.getSelection() : null;
2737
- const ownsSelection = this.contentSelectionAnchor && el.contains(this.contentSelectionAnchor.node) || (_optionalChain([selection, 'optionalAccess', _62 => _62.anchorNode]) ? el.contains(selection.anchorNode) : false) || (_optionalChain([selection, 'optionalAccess', _63 => _63.focusNode]) ? el.contains(selection.focusNode) : false);
2848
+ const ownsSelection = this.contentSelectionAnchor && el.contains(this.contentSelectionAnchor.node) || (_optionalChain([selection, 'optionalAccess', _68 => _68.anchorNode]) ? el.contains(selection.anchorNode) : false) || (_optionalChain([selection, 'optionalAccess', _69 => _69.focusNode]) ? el.contains(selection.focusNode) : false);
2738
2849
  if (!ownsSelection) return;
2739
2850
  this.endContentSelectionDrag();
2740
- _optionalChain([selection, 'optionalAccess', _64 => _64.removeAllRanges, 'call', _65 => _65()]);
2851
+ _optionalChain([selection, 'optionalAccess', _70 => _70.removeAllRanges, 'call', _71 => _71()]);
2741
2852
  }
2742
2853
  /**
2743
2854
  * Rebuild a content-projection element's DOM (`rebuild`) while preserving a
@@ -2796,7 +2907,7 @@ var Scene = (_class7 = class _Scene {
2796
2907
  }
2797
2908
  /** Convert browser viewport coordinates into this Scene's logical coordinates. */
2798
2909
  clientToScene(clientX, clientY) {
2799
- const rect = _optionalChain([this, 'access', _66 => _66.canvas, 'access', _67 => _67.getBoundingClientRect, 'optionalCall', _68 => _68()]);
2910
+ const rect = _optionalChain([this, 'access', _72 => _72.canvas, 'access', _73 => _73.getBoundingClientRect, 'optionalCall', _74 => _74()]);
2800
2911
  if (!rect) return { x: clientX, y: clientY };
2801
2912
  const cssWidth = rect.width || this.canvas.clientWidth || this.width;
2802
2913
  const cssHeight = rect.height || this.canvas.clientHeight || this.height;
@@ -2817,13 +2928,24 @@ var Scene = (_class7 = class _Scene {
2817
2928
  this.registerActiveDriverSubtree(entity);
2818
2929
  return this;
2819
2930
  }
2820
- clearContentGridState(entityId, el) {
2931
+ /**
2932
+ * Reset per-grid calibration and bookkeeping before a (re)materialization.
2933
+ *
2934
+ * @param entityId - Owning entity, keyed into the calibration maps.
2935
+ * @param el - The projection element.
2936
+ * @param releaseSelection - Whether to drop a selection this element owns.
2937
+ * Pass `false` when carrier lines are being reused: the selection's DOM nodes
2938
+ * survive the pass, so tearing it down would wipe a user's selection on every
2939
+ * streamed chunk — the exact bug `preserveContentSelectionAcrossRebuild`
2940
+ * exists to prevent on the non-grid path.
2941
+ */
2942
+ clearContentGridState(entityId, el, releaseSelection = true) {
2821
2943
  const calibrationFrame = this.contentGridCalibrationFrames.get(entityId);
2822
2944
  if (calibrationFrame !== void 0 && typeof cancelAnimationFrame === "function") {
2823
2945
  cancelAnimationFrame(calibrationFrame);
2824
2946
  }
2825
2947
  this.contentGridCalibrationFrames.delete(entityId);
2826
- _optionalChain([this, 'access', _69 => _69.contentGridCalibrationProbes, 'access', _70 => _70.get, 'call', _71 => _71(entityId), 'optionalAccess', _72 => _72.remove, 'call', _73 => _73()]);
2948
+ _optionalChain([this, 'access', _75 => _75.contentGridCalibrationProbes, 'access', _76 => _76.get, 'call', _77 => _77(entityId), 'optionalAccess', _78 => _78.remove, 'call', _79 => _79()]);
2827
2949
  this.contentGridCalibrationProbes.delete(entityId);
2828
2950
  delete el.dataset.vectoGridCalibrationPending;
2829
2951
  delete el.dataset.vectoGridCalibration;
@@ -2833,7 +2955,21 @@ var Scene = (_class7 = class _Scene {
2833
2955
  delete el.dataset.vectoGridMaterializeMs;
2834
2956
  delete el.dataset.vectoGridCalibrationSamples;
2835
2957
  delete el.dataset.vectoGridCalibrationMs;
2836
- this.releaseContentSelectionForRebuild(el);
2958
+ if (releaseSelection) this.releaseContentSelectionForRebuild(el);
2959
+ }
2960
+ /**
2961
+ * Drop any projected elements under `node` without touching the entity tree.
2962
+ *
2963
+ * Used when the walk reaches an invisible subtree: the entities stay put (a
2964
+ * later `show()` re-projects them), but nothing under here may remain
2965
+ * focusable or announced while hidden.
2966
+ */
2967
+ pruneA11ySubtree(node) {
2968
+ if (this.a11yElements.has(node.id) || this.contentElements.has(node.id)) {
2969
+ this.removeA11yRecursively(node);
2970
+ return;
2971
+ }
2972
+ for (const child of node.children) this.pruneA11ySubtree(child);
2837
2973
  }
2838
2974
  removeA11yRecursively(node) {
2839
2975
  if (node.isDOMPortal) {
@@ -2861,7 +2997,7 @@ var Scene = (_class7 = class _Scene {
2861
2997
  }
2862
2998
  if (this.hoveredA11yElements.has(el)) {
2863
2999
  this.hoveredA11yElements.delete(el);
2864
- node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("pointerleave", node, void 0, false));
3000
+ node.dispatchEvent(new (0, _chunkAGP4VLF4js.VectoJSEvent)("pointerleave", node, void 0, false));
2865
3001
  }
2866
3002
  this.preserveFocusOnRemoval(el);
2867
3003
  el.remove();
@@ -2963,12 +3099,12 @@ var Scene = (_class7 = class _Scene {
2963
3099
  this.canvasResizeObserver = null;
2964
3100
  }
2965
3101
  if (this.dprMediaQuery && this.dprChangeHandler) {
2966
- _optionalChain([this, 'access', _74 => _74.dprMediaQuery, 'access', _75 => _75.removeEventListener, 'optionalCall', _76 => _76("change", this.dprChangeHandler)]);
3102
+ _optionalChain([this, 'access', _80 => _80.dprMediaQuery, 'access', _81 => _81.removeEventListener, 'optionalCall', _82 => _82("change", this.dprChangeHandler)]);
2967
3103
  this.dprMediaQuery = null;
2968
3104
  this.dprChangeHandler = null;
2969
3105
  }
2970
3106
  if (this.forcedColorsQuery && this.forcedColorsChangeHandler) {
2971
- _optionalChain([this, 'access', _77 => _77.forcedColorsQuery, 'access', _78 => _78.removeEventListener, 'optionalCall', _79 => _79("change", this.forcedColorsChangeHandler)]);
3107
+ _optionalChain([this, 'access', _83 => _83.forcedColorsQuery, 'access', _84 => _84.removeEventListener, 'optionalCall', _85 => _85("change", this.forcedColorsChangeHandler)]);
2972
3108
  this.forcedColorsQuery = null;
2973
3109
  this.forcedColorsChangeHandler = null;
2974
3110
  }
@@ -2986,9 +3122,9 @@ var Scene = (_class7 = class _Scene {
2986
3122
  }
2987
3123
  this.pointerEventTarget = null;
2988
3124
  }
2989
- _optionalChain([this, 'access', _80 => _80.a11yRoot, 'optionalAccess', _81 => _81.remove, 'call', _82 => _82()]);
3125
+ _optionalChain([this, 'access', _86 => _86.a11yRoot, 'optionalAccess', _87 => _87.remove, 'call', _88 => _88()]);
2990
3126
  this.focusSentinel = null;
2991
- _optionalChain([this, 'access', _83 => _83.portalRoot, 'optionalAccess', _84 => _84.remove, 'call', _85 => _85()]);
3127
+ _optionalChain([this, 'access', _89 => _89.portalRoot, 'optionalAccess', _90 => _90.remove, 'call', _91 => _91()]);
2992
3128
  this.a11yElements.clear();
2993
3129
  for (const el of this.contentElements.values()) el.remove();
2994
3130
  this.contentElements.clear();
@@ -3011,10 +3147,10 @@ var Scene = (_class7 = class _Scene {
3011
3147
  }
3012
3148
  this.glContextLostHandler = null;
3013
3149
  this.glContextRestoredHandler = null;
3014
- _optionalChain([this, 'access', _86 => _86.pointRenderer, 'optionalAccess', _87 => _87.destroy, 'call', _88 => _88()]);
3015
- _optionalChain([this, 'access', _89 => _89.renderer, 'access', _90 => _90.dispose, 'optionalCall', _91 => _91()]);
3016
- _optionalChain([this, 'access', _92 => _92.glCanvas, 'optionalAccess', _93 => _93.remove, 'call', _94 => _94()]);
3017
- _optionalChain([this, 'access', _95 => _95.gpuCanvas, 'optionalAccess', _96 => _96.remove, 'call', _97 => _97()]);
3150
+ _optionalChain([this, 'access', _92 => _92.pointRenderer, 'optionalAccess', _93 => _93.destroy, 'call', _94 => _94()]);
3151
+ _optionalChain([this, 'access', _95 => _95.renderer, 'access', _96 => _96.dispose, 'optionalCall', _97 => _97()]);
3152
+ _optionalChain([this, 'access', _98 => _98.glCanvas, 'optionalAccess', _99 => _99.remove, 'call', _100 => _100()]);
3153
+ _optionalChain([this, 'access', _101 => _101.gpuCanvas, 'optionalAccess', _102 => _102.remove, 'call', _103 => _103()]);
3018
3154
  this.gpuCanvas = null;
3019
3155
  this.gpuContext = null;
3020
3156
  if (this.recoveryTimerId) {
@@ -3026,7 +3162,7 @@ var Scene = (_class7 = class _Scene {
3026
3162
  this.manager = null;
3027
3163
  }
3028
3164
  if (this.device) {
3029
- _optionalChain([this, 'access', _98 => _98.device, 'access', _99 => _99.destroy, 'optionalCall', _100 => _100()]);
3165
+ _optionalChain([this, 'access', _104 => _104.device, 'access', _105 => _105.destroy, 'optionalCall', _106 => _106()]);
3030
3166
  this.device = null;
3031
3167
  }
3032
3168
  }
@@ -3149,10 +3285,32 @@ var Scene = (_class7 = class _Scene {
3149
3285
  get overlayRootEntity() {
3150
3286
  return this.overlayRoot;
3151
3287
  }
3288
+ /**
3289
+ * Advance and render exactly one frame, synchronously.
3290
+ *
3291
+ * This renders UNCONDITIONALLY: it consults neither {@link renderMode} nor
3292
+ * {@link dirty}, and it does not apply the `always`-mode idle auto-throttle.
3293
+ * That is deliberate — a deterministic driver (video export, a test, a
3294
+ * fixed-step benchmark) asks for a frame because it wants that frame, not a
3295
+ * scheduler opinion about whether it is needed.
3296
+ *
3297
+ * The consequence is a measurement footgun worth stating explicitly: a
3298
+ * benchmark that drives frames through `step()` CANNOT observe frame skipping,
3299
+ * so `always` and `onDemand` produce byte-identical draw counts through this
3300
+ * path. An investigation into whether `onDemand` skips redundant repaints once
3301
+ * concluded "it does not" on exactly that basis; on the real rAF loop the same
3302
+ * workload rendered ~1.0 frames per content change. To measure anything about
3303
+ * scheduling, use {@link start} and let `requestAnimationFrame` drive.
3304
+ *
3305
+ * @param dt Seconds to advance. Not clamped by `MAX_FRAME_DT` — the caller
3306
+ * chooses the step, since determinism is the point.
3307
+ */
3152
3308
  step(dt) {
3153
3309
  const time = this.lastTime + dt;
3154
3310
  this.lastTime = time;
3311
+ const t0 = this._phaseTiming ? performance.now() : 0;
3155
3312
  this.render(this.renderer, dt, time);
3313
+ if (this._phaseTiming) this._recordPhase("render", performance.now() - t0);
3156
3314
  this.dirty = false;
3157
3315
  }
3158
3316
  /**
@@ -3165,6 +3323,24 @@ var Scene = (_class7 = class _Scene {
3165
3323
  this.dirty = true;
3166
3324
  if (this._dirtyTracking && source) this.recordDirtyReason(source);
3167
3325
  }
3326
+ /**
3327
+ * Increments whenever the tree's shape changes: add, remove or reparent.
3328
+ *
3329
+ * Already maintained for the resident WASM transform store (see
3330
+ * {@link markStructureChanged}, called from `Entity.add`/`remove`), and exposed
3331
+ * here because a cache of the tree's shape — a DevTools tree model, a serialized
3332
+ * snapshot — is valid exactly as long as this value is unchanged. Comparing it is
3333
+ * O(1) against re-walking the tree, which is what it replaces: DevTools rebuilt
3334
+ * both trees on a fixed 500 ms interval, a constant cost proportional to entity
3335
+ * count, purely because it had no way to ask whether the shape had changed.
3336
+ *
3337
+ * Property changes do NOT bump it. Moving or restyling an entity leaves the
3338
+ * shape intact, so a consumer that also cares about values must read those
3339
+ * directly rather than rebuilding a tree.
3340
+ */
3341
+ get structureVersion() {
3342
+ return this._structureVersion;
3343
+ }
3168
3344
  /**
3169
3345
  * Record who marked the scene dirty and why.
3170
3346
  *
@@ -3287,6 +3463,11 @@ var Scene = (_class7 = class _Scene {
3287
3463
  if (node.isDOMPortal) {
3288
3464
  return;
3289
3465
  }
3466
+ if (node.a11yHidden) {
3467
+ this.pruneA11ySubtree(node);
3468
+ return;
3469
+ }
3470
+ const nodeStart = this._phaseTiming ? performance.now() : 0;
3290
3471
  if (this.shouldProjectA11y(node)) {
3291
3472
  let el = this.a11yElements.get(node.id);
3292
3473
  const attrs = node.getA11yAttributes();
@@ -3328,20 +3509,20 @@ var Scene = (_class7 = class _Scene {
3328
3509
  el.style.background = "transparent";
3329
3510
  }
3330
3511
  el.addEventListener("click", (e) => {
3331
- node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("click", node, e));
3512
+ node.dispatchEvent(new (0, _chunkAGP4VLF4js.VectoJSEvent)("click", node, e));
3332
3513
  });
3333
3514
  el.addEventListener("dblclick", (e) => {
3334
- node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("dblclick", node, e));
3515
+ node.dispatchEvent(new (0, _chunkAGP4VLF4js.VectoJSEvent)("dblclick", node, e));
3335
3516
  });
3336
3517
  el.addEventListener("mouseenter", (e) => {
3337
3518
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.2)";
3338
3519
  this.hoveredA11yElements.add(el);
3339
- node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("hover", node, e, false));
3520
+ node.dispatchEvent(new (0, _chunkAGP4VLF4js.VectoJSEvent)("hover", node, e, false));
3340
3521
  });
3341
3522
  el.addEventListener("mouseleave", (e) => {
3342
3523
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.05)";
3343
3524
  this.hoveredA11yElements.delete(el);
3344
- node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("pointerleave", node, e, false));
3525
+ node.dispatchEvent(new (0, _chunkAGP4VLF4js.VectoJSEvent)("pointerleave", node, e, false));
3345
3526
  });
3346
3527
  const capEl = el;
3347
3528
  const releasePointer = (event) => {
@@ -3357,32 +3538,32 @@ var Scene = (_class7 = class _Scene {
3357
3538
  };
3358
3539
  el.addEventListener("pointerdown", (e) => {
3359
3540
  if (typeof capEl.setPointerCapture === "function") capEl.setPointerCapture(e.pointerId);
3360
- node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("pointerdown", node, e));
3541
+ node.dispatchEvent(new (0, _chunkAGP4VLF4js.VectoJSEvent)("pointerdown", node, e));
3361
3542
  });
3362
3543
  el.addEventListener("pointerup", (e) => {
3363
3544
  releasePointer(e);
3364
- node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("pointerup", node, e));
3545
+ node.dispatchEvent(new (0, _chunkAGP4VLF4js.VectoJSEvent)("pointerup", node, e));
3365
3546
  });
3366
3547
  el.addEventListener("pointercancel", (e) => {
3367
3548
  releasePointer(e);
3368
- node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("pointercancel", node, e));
3549
+ node.dispatchEvent(new (0, _chunkAGP4VLF4js.VectoJSEvent)("pointercancel", node, e));
3369
3550
  });
3370
3551
  el.addEventListener(
3371
3552
  "pointermove",
3372
- (e) => node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("pointermove", node, e))
3553
+ (e) => node.dispatchEvent(new (0, _chunkAGP4VLF4js.VectoJSEvent)("pointermove", node, e))
3373
3554
  );
3374
3555
  el.addEventListener(
3375
3556
  "wheel",
3376
3557
  (e) => {
3377
- node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("wheel", node, e));
3558
+ node.dispatchEvent(new (0, _chunkAGP4VLF4js.VectoJSEvent)("wheel", node, e));
3378
3559
  },
3379
3560
  { passive: false }
3380
3561
  );
3381
3562
  el.addEventListener("keydown", (e) => {
3382
- node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("keydown", node, e));
3563
+ node.dispatchEvent(new (0, _chunkAGP4VLF4js.VectoJSEvent)("keydown", node, e));
3383
3564
  });
3384
3565
  el.addEventListener("keyup", (e) => {
3385
- node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("keyup", node, e));
3566
+ node.dispatchEvent(new (0, _chunkAGP4VLF4js.VectoJSEvent)("keyup", node, e));
3386
3567
  });
3387
3568
  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
3388
3569
  const input = el;
@@ -3413,7 +3594,7 @@ var Scene = (_class7 = class _Scene {
3413
3594
  el.addEventListener("compositionupdate", (e) => {
3414
3595
  const data = _nullishCoalesce(e.data, () => ( ""));
3415
3596
  composition = {
3416
- start: _nullishCoalesce(_optionalChain([composition, 'optionalAccess', _101 => _101.start]), () => ( 0)),
3597
+ start: _nullishCoalesce(_optionalChain([composition, 'optionalAccess', _107 => _107.start]), () => ( 0)),
3417
3598
  length: data.length
3418
3599
  };
3419
3600
  forward();
@@ -3448,7 +3629,7 @@ var Scene = (_class7 = class _Scene {
3448
3629
  el.addEventListener("keydown", (e) => {
3449
3630
  if (e.key === "Enter" || e.key === " ") {
3450
3631
  e.preventDefault();
3451
- node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("click", node, e));
3632
+ node.dispatchEvent(new (0, _chunkAGP4VLF4js.VectoJSEvent)("click", node, e));
3452
3633
  }
3453
3634
  });
3454
3635
  }
@@ -3486,7 +3667,7 @@ var Scene = (_class7 = class _Scene {
3486
3667
  this.syncOptionalAttribute(
3487
3668
  el,
3488
3669
  "href",
3489
- attrs.href === void 0 ? void 0 : _chunkL4SWVP2Hjs.sanitizeUrl.call(void 0, attrs.href)
3670
+ attrs.href === void 0 ? void 0 : _chunkGKSCJ6AFjs.sanitizeUrl.call(void 0, attrs.href)
3490
3671
  );
3491
3672
  this.syncOptionalAttribute(el, "target", attrs.target);
3492
3673
  }
@@ -3629,7 +3810,14 @@ var Scene = (_class7 = class _Scene {
3629
3810
  if (el.style.display !== display) el.style.display = display;
3630
3811
  }
3631
3812
  }
3632
- this.syncContentProjection(node);
3813
+ if (this._phaseTiming) {
3814
+ const projectionStart = performance.now();
3815
+ this.syncContentProjection(node);
3816
+ this._recordPhase("contentProjection", performance.now() - projectionStart);
3817
+ this._recordPhase("a11yNodes", projectionStart - nodeStart);
3818
+ } else {
3819
+ this.syncContentProjection(node);
3820
+ }
3633
3821
  for (const child of node.children) this.syncA11y(child);
3634
3822
  if (node === this.root) {
3635
3823
  for (const overlay of this.overlayRoot.children) this.syncA11y(overlay);
@@ -3730,7 +3918,7 @@ var Scene = (_class7 = class _Scene {
3730
3918
  el.addEventListener(
3731
3919
  "wheel",
3732
3920
  (e2) => {
3733
- node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("wheel", node, e2));
3921
+ node.dispatchEvent(new (0, _chunkAGP4VLF4js.VectoJSEvent)("wheel", node, e2));
3734
3922
  },
3735
3923
  { passive: false }
3736
3924
  );
@@ -3743,7 +3931,9 @@ var Scene = (_class7 = class _Scene {
3743
3931
  this.clearContentGridState(node.id, el);
3744
3932
  }
3745
3933
  if (projection.grid) {
3934
+ const gridSyncStart = this._phaseTiming ? performance.now() : 0;
3746
3935
  this.syncContentGridProjection(node, el, projection, projection.grid);
3936
+ if (this._phaseTiming) this._recordPhase("gridSync", performance.now() - gridSyncStart);
3747
3937
  } else if (lines && lines.length > 0) {
3748
3938
  const signature = JSON.stringify({
3749
3939
  lines,
@@ -3849,21 +4039,38 @@ var Scene = (_class7 = class _Scene {
3849
4039
  const signature = `${grid.revision}`;
3850
4040
  if (el.dataset.vectoContentGrid !== signature) {
3851
4041
  const materializeStart = typeof performance !== "undefined" ? performance.now() : 0;
3852
- this.clearContentGridState(node.id, el);
3853
- el.replaceChildren();
4042
+ this.clearContentGridState(node.id, el, false);
3854
4043
  const projectionLines = _nullishCoalesce(projection.lines, () => ( []));
4044
+ const selectionLine = this.contentGridSelectionLine(el);
4045
+ let rebuiltSelectionLine = false;
4046
+ const existingLines = el.children;
3855
4047
  for (let lineIndex = 0; lineIndex < grid.lines.length; lineIndex++) {
3856
4048
  const gridLine = grid.lines[lineIndex];
3857
4049
  const projectedLine = projectionLines[lineIndex];
3858
- const lineHeight = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _102 => _102.lineHeight]), () => ( grid.lineHeight));
3859
- const baseline = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _103 => _103.baseline]), () => ( grid.baseline));
3860
- const lineFont = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _104 => _104.font]), () => ( grid.font));
4050
+ const lineHeight = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _108 => _108.lineHeight]), () => ( grid.lineHeight));
4051
+ const baseline = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _109 => _109.baseline]), () => ( grid.baseline));
4052
+ const lineFont = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _110 => _110.font]), () => ( grid.font));
4053
+ const lineSignature = contentGridLineSignature(
4054
+ grid,
4055
+ gridLine,
4056
+ projectedLine,
4057
+ lineHeight,
4058
+ baseline,
4059
+ lineFont,
4060
+ lineIndex === 0
4061
+ );
4062
+ const reusable = existingLines[lineIndex];
4063
+ if (reusable !== void 0 && reusable.dataset.vectoGridLineSig === lineSignature && reusable.dataset.vectoGridLine === `${lineIndex}`) {
4064
+ continue;
4065
+ }
4066
+ if (selectionLine !== null && selectionLine === lineIndex) rebuiltSelectionLine = true;
3861
4067
  const lineElement = document.createElement("span");
4068
+ lineElement.dataset.vectoGridLineSig = lineSignature;
3862
4069
  lineElement.dir = "ltr";
3863
4070
  lineElement.dataset.vectoGridLine = `${lineIndex}`;
3864
4071
  lineElement.style.position = "absolute";
3865
- lineElement.style.left = `${_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _105 => _105.x]), () => ( 0))}px`;
3866
- lineElement.style.top = `${(_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _106 => _106.y]), () => ( lineIndex * grid.lineHeight))) + baseline - _text.cssLineBoxBaseline.call(void 0, lineFont, lineHeight)}px`;
4072
+ lineElement.style.left = `${_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _111 => _111.x]), () => ( 0))}px`;
4073
+ lineElement.style.top = `${(_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _112 => _112.y]), () => ( lineIndex * grid.lineHeight))) + baseline - _text.cssLineBoxBaseline.call(void 0, lineFont, lineHeight)}px`;
3867
4074
  lineElement.style.width = `${gridLine.width}px`;
3868
4075
  lineElement.style.height = `${lineHeight}px`;
3869
4076
  lineElement.style.whiteSpace = "pre";
@@ -3900,6 +4107,8 @@ var Scene = (_class7 = class _Scene {
3900
4107
  cellElement.style.font = lineFont;
3901
4108
  cellElement.style.lineHeight = `${lineHeight}px`;
3902
4109
  cellElement.style.transformOrigin = "0 50%";
4110
+ cellElement.dataset.vectoGridFont = lineFont;
4111
+ cellElement.dataset.vectoGridLineHeight = `${lineHeight}px`;
3903
4112
  lineElement.appendChild(cellElement);
3904
4113
  logicalX += cell.advance;
3905
4114
  }
@@ -3923,13 +4132,24 @@ var Scene = (_class7 = class _Scene {
3923
4132
  lineElement.appendChild(marker);
3924
4133
  }
3925
4134
  }
3926
- el.appendChild(lineElement);
4135
+ const occupant = el.children[lineIndex];
4136
+ if (occupant) el.replaceChild(lineElement, occupant);
4137
+ else el.appendChild(lineElement);
4138
+ }
4139
+ while (el.children.length > grid.lines.length) {
4140
+ if (selectionLine !== null && selectionLine >= grid.lines.length) {
4141
+ rebuiltSelectionLine = true;
4142
+ }
4143
+ _optionalChain([el, 'access', _113 => _113.lastElementChild, 'optionalAccess', _114 => _114.remove, 'call', _115 => _115()]);
3927
4144
  }
4145
+ if (rebuiltSelectionLine) this.releaseContentSelectionForRebuild(el);
3928
4146
  el.dataset.vectoProjectionLines = signature;
3929
4147
  el.dataset.vectoContentGrid = signature;
3930
4148
  el.dataset.vectoGridCarriers = `${el.querySelectorAll("[data-vecto-grid-cell]").length}`;
3931
4149
  if (typeof performance !== "undefined") {
3932
- el.dataset.vectoGridMaterializeMs = `${performance.now() - materializeStart}`;
4150
+ const materializeMs = performance.now() - materializeStart;
4151
+ el.dataset.vectoGridMaterializeMs = `${materializeMs}`;
4152
+ if (this._phaseTiming) this._recordPhase("gridMaterialize", materializeMs);
3933
4153
  }
3934
4154
  delete el.dataset.vectoGridCalibration;
3935
4155
  delete el.dataset.vectoGridReady;
@@ -3937,7 +4157,11 @@ var Scene = (_class7 = class _Scene {
3937
4157
  const pageScaleX = this.getContentMetricScaleX();
3938
4158
  const calibrationKey = `${signature}:${this.contentFontEpoch}:${pageScaleX.toFixed(4)}`;
3939
4159
  if (el.dataset.vectoGridCalibration !== calibrationKey) {
4160
+ const calibStart = this._phaseTiming ? performance.now() : 0;
3940
4161
  this.scheduleContentGridCalibration(node.id, el, calibrationKey, pageScaleX);
4162
+ if (this._phaseTiming) {
4163
+ this._recordPhase("gridCalibrateSchedule", performance.now() - calibStart);
4164
+ }
3941
4165
  }
3942
4166
  }
3943
4167
  getContentMetricScaleX() {
@@ -3955,11 +4179,32 @@ var Scene = (_class7 = class _Scene {
3955
4179
  scheduleContentGridCalibration(entityId, el, calibrationKey, pageScaleX) {
3956
4180
  if (typeof requestAnimationFrame !== "function") return;
3957
4181
  if (el.dataset.vectoGridCalibrationPending === calibrationKey) return;
4182
+ const stamp = `${this.contentFontEpoch}:${pageScaleX.toFixed(4)}`;
4183
+ if (this.contentGridCalibrationStamp !== stamp) {
4184
+ this.contentGridCalibrationStamp = stamp;
4185
+ this.contentGridCalibrationGeneration++;
4186
+ }
4187
+ const generation = `${this.contentGridCalibrationGeneration}`;
4188
+ const pendingCells = el.querySelectorAll(
4189
+ `[data-vecto-grid-cell]:not([data-vecto-grid-calib="${generation}"])`
4190
+ );
4191
+ if (pendingCells.length === 0) {
4192
+ el.dataset.vectoGridCalibrationSamples = "0";
4193
+ delete el.dataset.vectoGridCalibrationPending;
4194
+ const readyFrame = requestAnimationFrame(() => {
4195
+ this.contentGridCalibrationFrames.delete(entityId);
4196
+ if (!el.isConnected) return;
4197
+ el.dataset.vectoGridCalibration = calibrationKey;
4198
+ el.dataset.vectoGridReady = "true";
4199
+ });
4200
+ this.contentGridCalibrationFrames.set(entityId, readyFrame);
4201
+ return;
4202
+ }
3958
4203
  const previous = this.contentGridCalibrationFrames.get(entityId);
3959
4204
  if (previous !== void 0 && typeof cancelAnimationFrame === "function") {
3960
4205
  cancelAnimationFrame(previous);
3961
4206
  }
3962
- _optionalChain([this, 'access', _107 => _107.contentGridCalibrationProbes, 'access', _108 => _108.get, 'call', _109 => _109(entityId), 'optionalAccess', _110 => _110.remove, 'call', _111 => _111()]);
4207
+ _optionalChain([this, 'access', _116 => _116.contentGridCalibrationProbes, 'access', _117 => _117.get, 'call', _118 => _118(entityId), 'optionalAccess', _119 => _119.remove, 'call', _120 => _120()]);
3963
4208
  this.contentGridCalibrationProbes.delete(entityId);
3964
4209
  const calibrationStart = typeof performance !== "undefined" ? performance.now() : 0;
3965
4210
  const probe = document.createElement("div");
@@ -3985,18 +4230,22 @@ var Scene = (_class7 = class _Scene {
3985
4230
  probe.append(probeOrigin, probeX);
3986
4231
  const measurements = [];
3987
4232
  const measurementsByKey = /* @__PURE__ */ new Map();
3988
- for (const target of el.querySelectorAll("[data-vecto-grid-cell]")) {
4233
+ const scanStart = this._phaseTiming ? performance.now() : 0;
4234
+ for (const target of pendingCells) {
3989
4235
  const sourceLength = Number(_nullishCoalesce(target.dataset.vectoGridSourceLength, () => ( 0)));
3990
4236
  const targetWidth = Number(_nullishCoalesce(target.dataset.vectoGridAdvance, () => ( 0)));
3991
- if (sourceLength <= 0 || targetWidth <= 0) continue;
3992
- const sourceText = _nullishCoalesce(_optionalChain([target, 'access', _112 => _112.textContent, 'optionalAccess', _113 => _113.slice, 'call', _114 => _114(0, sourceLength)]), () => ( ""));
3993
- if (!sourceText) continue;
3994
- const measurementKey = JSON.stringify([
3995
- target.style.font,
3996
- target.style.lineHeight,
3997
- targetWidth,
3998
- sourceText
3999
- ]);
4237
+ if (sourceLength <= 0 || targetWidth <= 0) {
4238
+ target.dataset.vectoGridCalib = generation;
4239
+ continue;
4240
+ }
4241
+ const sourceText = _nullishCoalesce(_optionalChain([target, 'access', _121 => _121.textContent, 'optionalAccess', _122 => _122.slice, 'call', _123 => _123(0, sourceLength)]), () => ( ""));
4242
+ if (!sourceText) {
4243
+ target.dataset.vectoGridCalib = generation;
4244
+ continue;
4245
+ }
4246
+ const cellFont = _nullishCoalesce(target.dataset.vectoGridFont, () => ( ""));
4247
+ const cellLineHeight = _nullishCoalesce(target.dataset.vectoGridLineHeight, () => ( ""));
4248
+ const measurementKey = JSON.stringify([cellFont, cellLineHeight, targetWidth, sourceText]);
4000
4249
  const shared = measurementsByKey.get(measurementKey);
4001
4250
  if (shared) {
4002
4251
  shared.targets.push(target);
@@ -4008,8 +4257,8 @@ var Scene = (_class7 = class _Scene {
4008
4257
  carrier.style.left = "0";
4009
4258
  carrier.style.top = "0";
4010
4259
  carrier.style.whiteSpace = "pre";
4011
- carrier.style.font = target.style.font;
4012
- carrier.style.lineHeight = target.style.lineHeight;
4260
+ carrier.style.font = cellFont;
4261
+ carrier.style.lineHeight = cellLineHeight;
4013
4262
  carrier.style.fontVariantLigatures = "none";
4014
4263
  carrier.style.fontKerning = "none";
4015
4264
  const source = document.createTextNode(sourceText);
@@ -4024,7 +4273,17 @@ var Scene = (_class7 = class _Scene {
4024
4273
  measurements.push(measurement);
4025
4274
  measurementsByKey.set(measurementKey, measurement);
4026
4275
  }
4276
+ if (this._phaseTiming) this._recordPhase("calibScan", performance.now() - scanStart);
4277
+ if (measurements.length === 0) {
4278
+ el.dataset.vectoGridCalibration = calibrationKey;
4279
+ el.dataset.vectoGridReady = "true";
4280
+ el.dataset.vectoGridCalibrationSamples = "0";
4281
+ delete el.dataset.vectoGridCalibrationPending;
4282
+ return;
4283
+ }
4284
+ const appendStart = this._phaseTiming ? performance.now() : 0;
4027
4285
  (_nullishCoalesce(_nullishCoalesce(this.a11yRoot, () => ( document.body)), () => ( document.documentElement))).appendChild(probe);
4286
+ if (this._phaseTiming) this._recordPhase("calibProbeBuild", performance.now() - appendStart);
4028
4287
  el.dataset.vectoGridCalibrationSamples = `${measurements.length}`;
4029
4288
  this.contentGridCalibrationProbes.set(entityId, probe);
4030
4289
  el.dataset.vectoGridCalibrationPending = calibrationKey;
@@ -4068,6 +4327,7 @@ var Scene = (_class7 = class _Scene {
4068
4327
  }
4069
4328
  for (const { element, scale } of updates) {
4070
4329
  element.style.transform = Math.abs(scale - 1) <= 1e-3 ? "" : `scaleX(${scale})`;
4330
+ element.dataset.vectoGridCalib = generation;
4071
4331
  }
4072
4332
  el.dataset.vectoGridCalibration = calibrationKey;
4073
4333
  el.dataset.vectoGridReady = "true";
@@ -4189,12 +4449,12 @@ var Scene = (_class7 = class _Scene {
4189
4449
  syncOverlayGeometry() {
4190
4450
  const parent = this.canvas.parentElement;
4191
4451
  if (!parent) return;
4192
- const canvasRect = _optionalChain([this, 'access', _115 => _115.canvas, 'access', _116 => _116.getBoundingClientRect, 'optionalCall', _117 => _117()]);
4193
- const parentRect = _optionalChain([parent, 'access', _118 => _118.getBoundingClientRect, 'optionalCall', _119 => _119()]);
4194
- const cssWidth = _optionalChain([canvasRect, 'optionalAccess', _120 => _120.width]) || this.canvas.clientWidth || this.width;
4195
- const cssHeight = _optionalChain([canvasRect, 'optionalAccess', _121 => _121.height]) || this.canvas.clientHeight || this.height;
4196
- const left = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _122 => _122.left]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _123 => _123.left]), () => ( 0))) - (parent.clientLeft || 0) + parent.scrollLeft;
4197
- const top = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _124 => _124.top]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _125 => _125.top]), () => ( 0))) - (parent.clientTop || 0) + parent.scrollTop;
4452
+ const canvasRect = _optionalChain([this, 'access', _124 => _124.canvas, 'access', _125 => _125.getBoundingClientRect, 'optionalCall', _126 => _126()]);
4453
+ const parentRect = _optionalChain([parent, 'access', _127 => _127.getBoundingClientRect, 'optionalCall', _128 => _128()]);
4454
+ const cssWidth = _optionalChain([canvasRect, 'optionalAccess', _129 => _129.width]) || this.canvas.clientWidth || this.width;
4455
+ const cssHeight = _optionalChain([canvasRect, 'optionalAccess', _130 => _130.height]) || this.canvas.clientHeight || this.height;
4456
+ const left = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _131 => _131.left]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _132 => _132.left]), () => ( 0))) - (parent.clientLeft || 0) + parent.scrollLeft;
4457
+ const top = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _133 => _133.top]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _134 => _134.top]), () => ( 0))) - (parent.clientTop || 0) + parent.scrollTop;
4198
4458
  const scaleX = this.width > 0 ? cssWidth / this.width : 1;
4199
4459
  const scaleY = this.height > 0 ? cssHeight / this.height : 1;
4200
4460
  const prev = this._overlayGeometry;
@@ -4333,7 +4593,7 @@ var Scene = (_class7 = class _Scene {
4333
4593
  * (and {@link respectReducedMotion} is on). `0` means uncapped.
4334
4594
  */
4335
4595
  effectiveMaxFPS() {
4336
- const reduced = this.respectReducedMotion && !!_optionalChain([this, 'access', _126 => _126.reducedMotionQuery, 'optionalAccess', _127 => _127.matches]);
4596
+ const reduced = this.respectReducedMotion && !!_optionalChain([this, 'access', _135 => _135.reducedMotionQuery, 'optionalAccess', _136 => _136.matches]);
4337
4597
  if (reduced)
4338
4598
  return this.maxFPS > 0 ? Math.min(this.maxFPS, REDUCED_MOTION_FPS) : REDUCED_MOTION_FPS;
4339
4599
  return this.maxFPS;
@@ -4373,7 +4633,9 @@ var Scene = (_class7 = class _Scene {
4373
4633
  }
4374
4634
  this._lastRenderTick = time;
4375
4635
  this._lastDt = dt;
4636
+ const phaseClock = this._phaseTiming ? performance.now() : 0;
4376
4637
  this.render(this.renderer, dt, time);
4638
+ if (this._phaseTiming) this._recordPhase("render", performance.now() - phaseClock);
4377
4639
  this._lastFrameMs = (typeof performance !== "undefined" ? performance.now() : time) - now;
4378
4640
  this._renderedFrames++;
4379
4641
  const hasActiveAnimation = this.frameHadAnimation;
@@ -4383,9 +4645,13 @@ var Scene = (_class7 = class _Scene {
4383
4645
  if ((hasInteractive || this.a11yElements.size > 0 || wantsContentSync) && (shouldSyncInterval || this.a11yPendingSyncAfterAnimation)) {
4384
4646
  this.lastA11ySync = time;
4385
4647
  if (hasInteractive || wantsContentSync) {
4648
+ const t0 = this._phaseTiming ? performance.now() : 0;
4386
4649
  this.syncA11y(this.root);
4650
+ if (this._phaseTiming) this._recordPhase("a11ySync", performance.now() - t0);
4387
4651
  }
4652
+ const t1 = this._phaseTiming ? performance.now() : 0;
4388
4653
  this.enforceA11yDomOrder();
4654
+ if (this._phaseTiming) this._recordPhase("a11yOrder", performance.now() - t1);
4389
4655
  this.a11yPendingSyncAfterAnimation = hasActiveAnimation;
4390
4656
  } else if (hasActiveAnimation) {
4391
4657
  this.a11yPendingSyncAfterAnimation = true;
@@ -4400,7 +4666,7 @@ var Scene = (_class7 = class _Scene {
4400
4666
  * @param time - Current absolute time in milliseconds (default 0).
4401
4667
  */
4402
4668
  render(renderer, dt = 0, time = 0) {
4403
- if (_optionalChain([renderer, 'access', _128 => _128.isContextLost, 'optionalCall', _129 => _129()])) return;
4669
+ if (_optionalChain([renderer, 'access', _137 => _137.isContextLost, 'optionalCall', _138 => _138()])) return;
4404
4670
  const isMainRenderer = renderer === this.renderer;
4405
4671
  if (isMainRenderer && this.a11yRoot && this.canvas.parentElement) {
4406
4672
  const parentStyle = this.canvas.parentElement.style;
@@ -4526,14 +4792,14 @@ var Scene = (_class7 = class _Scene {
4526
4792
  }
4527
4793
  renderer.clear();
4528
4794
  if (isMainRenderer) {
4529
- _optionalChain([this, 'access', _130 => _130.pointRenderer, 'optionalAccess', _131 => _131.begin, 'call', _132 => _132()]);
4795
+ _optionalChain([this, 'access', _139 => _139.pointRenderer, 'optionalAccess', _140 => _140.begin, 'call', _141 => _141()]);
4530
4796
  }
4531
4797
  const vw = this.width;
4532
4798
  const vh = this.height;
4533
4799
  let walkHadAnimation = false;
4534
4800
  let walkHadInteractive = false;
4535
4801
  const runUpdate = (node) => {
4536
- const overridesUpdate = node.update !== _chunkIA3KW4CGjs.Entity.prototype.update;
4802
+ const overridesUpdate = node.update !== _chunkAGP4VLF4js.Entity.prototype.update;
4537
4803
  let pending = node.hasPendingAnimations();
4538
4804
  if (pending || overridesUpdate) {
4539
4805
  node.update(dt, time);
@@ -4542,7 +4808,7 @@ var Scene = (_class7 = class _Scene {
4542
4808
  if (pending) walkHadAnimation = true;
4543
4809
  if (!walkHadInteractive && node.interactive) walkHadInteractive = true;
4544
4810
  if (this._devActive && this._devFrameCount % 120 === 0) {
4545
- if (overridesUpdate && node.hasPendingAnimations === _chunkIA3KW4CGjs.Entity.prototype.hasPendingAnimations) {
4811
+ if (overridesUpdate && node.hasPendingAnimations === _chunkAGP4VLF4js.Entity.prototype.hasPendingAnimations) {
4546
4812
  this._devWarn(
4547
4813
  `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.`
4548
4814
  );
@@ -4559,7 +4825,9 @@ var Scene = (_class7 = class _Scene {
4559
4825
  updateWalk(this.root);
4560
4826
  for (const overlay of this.overlayRoot.children) updateWalk(overlay);
4561
4827
  }
4828
+ const wasmT0 = this._phaseTiming ? performance.now() : 0;
4562
4829
  const wasmWorld = wasmMain ? this._syncWasmStore() : null;
4830
+ if (this._phaseTiming) this._recordPhase("transform", performance.now() - wasmT0);
4563
4831
  const wasmSlotEntity = this._slotEntity;
4564
4832
  const renderNode = (node, pa, pb, pc, pd, pe, pf, parentOpacity) => {
4565
4833
  if (isMainRenderer && !wasmMain) {
@@ -4689,7 +4957,13 @@ var Scene = (_class7 = class _Scene {
4689
4957
  );
4690
4958
  }
4691
4959
  } else {
4692
- node.render(renderer);
4960
+ if (this._phaseTiming) {
4961
+ const t0 = performance.now();
4962
+ node.render(renderer);
4963
+ this._recordPhase("entityPaint", performance.now() - t0);
4964
+ } else {
4965
+ node.render(renderer);
4966
+ }
4693
4967
  }
4694
4968
  }
4695
4969
  if (node.clipChildren) {
@@ -4701,20 +4975,24 @@ var Scene = (_class7 = class _Scene {
4701
4975
  renderer.flush();
4702
4976
  renderer.restore();
4703
4977
  };
4978
+ const drawT0 = this._phaseTiming ? performance.now() : 0;
4704
4979
  renderNode(this.root, 1, 0, 0, 1, 0, 0, 1);
4705
4980
  for (const overlay of this.overlayRoot.children) {
4706
4981
  renderNode(overlay, 1, 0, 0, 1, 0, 0, 1);
4707
4982
  }
4983
+ if (this._phaseTiming) this._recordPhase("drawWalk", performance.now() - drawT0);
4708
4984
  if (isMainRenderer) {
4709
4985
  this.frameHadAnimation = walkHadAnimation;
4710
4986
  this.frameHadInteractive = walkHadInteractive;
4711
4987
  this.reconcilePortals();
4712
4988
  }
4989
+ const flushT0 = this._phaseTiming ? performance.now() : 0;
4713
4990
  renderer.flush();
4714
4991
  if (isMainRenderer) {
4715
- _optionalChain([this, 'access', _133 => _133.pointRenderer, 'optionalAccess', _134 => _134.flush, 'call', _135 => _135()]);
4992
+ _optionalChain([this, 'access', _142 => _142.pointRenderer, 'optionalAccess', _143 => _143.flush, 'call', _144 => _144()]);
4716
4993
  }
4717
- _optionalChain([renderer, 'access', _136 => _136.present, 'optionalCall', _137 => _137()]);
4994
+ _optionalChain([renderer, 'access', _145 => _145.present, 'optionalCall', _146 => _146()]);
4995
+ if (this._phaseTiming) this._recordPhase("flush", performance.now() - flushT0);
4718
4996
  if (this._devActive) {
4719
4997
  this._devFrameCount++;
4720
4998
  this._devRunChecks();
@@ -4724,7 +5002,7 @@ var Scene = (_class7 = class _Scene {
4724
5002
  * Export the current scene state to a lightweight, flat SVG XML string.
4725
5003
  */
4726
5004
  toSVG() {
4727
- const renderer = new (0, _chunkL4SWVP2Hjs.SVGRenderer)(this.width, this.height);
5005
+ const renderer = new (0, _chunkGKSCJ6AFjs.SVGRenderer)(this.width, this.height);
4728
5006
  this.render(renderer, 0, 0);
4729
5007
  return renderer.toXMLString();
4730
5008
  }
@@ -4990,6 +5268,41 @@ function intersectBounds(a, b) {
4990
5268
  function pointInBounds(b, x, y) {
4991
5269
  return x >= b.x && x <= b.x + b.width && y >= b.y && y <= b.y + b.height;
4992
5270
  }
5271
+ function contentGridLineSignature(grid, line, projected, lineHeight, baseline, font, isFirstLine) {
5272
+ const parts = [
5273
+ // Line box: position, size, and the font that resolves its baseline.
5274
+ `${_nullishCoalesce(_optionalChain([projected, 'optionalAccess', _147 => _147.x]), () => ( 0))}`,
5275
+ `${_nullishCoalesce(_optionalChain([projected, 'optionalAccess', _148 => _148.y]), () => ( ""))}`,
5276
+ `${lineHeight}`,
5277
+ `${baseline}`,
5278
+ font,
5279
+ `${line.width}`,
5280
+ // The trailing hard break belongs to this line and lands in the DOM text.
5281
+ grid.source.slice(line.sourceEnd, line.nextSourceStart),
5282
+ // The basis markers are appended only to line 0, so a line moving to or from
5283
+ // index 0 changes its DOM even when nothing else does.
5284
+ isFirstLine ? "1" : "0"
5285
+ ];
5286
+ if (line.cells.length === 0) {
5287
+ parts.push("empty");
5288
+ } else {
5289
+ for (const cell of line.cells) {
5290
+ parts.push(
5291
+ `${cell.sourceStart}`,
5292
+ `${cell.sourceEnd}`,
5293
+ `${cell.x}`,
5294
+ `${cell.advance}`,
5295
+ `${cell.level}`,
5296
+ cell.sourceCaretOffsets.join("."),
5297
+ // Source text, not `cell.glyph`: the carrier holds the original characters
5298
+ // (the shaped glyph is the canvas's business), so a change in shaping alone
5299
+ // must not invalidate a carrier, and a change in source must.
5300
+ grid.source.slice(cell.sourceStart, cell.sourceEnd)
5301
+ );
5302
+ }
5303
+ }
5304
+ return parts.join("");
5305
+ }
4993
5306
 
4994
5307
  // src/components/TextEntity.ts
4995
5308
 
@@ -5001,20 +5314,20 @@ function defaultMeasurer() {
5001
5314
  if (sharedMeasurer === void 0) sharedMeasurer = _layout.createCanvasMeasurer.call(void 0, "sans-serif");
5002
5315
  return sharedMeasurer;
5003
5316
  }
5004
- var TextEntity = (_class8 = class extends _chunkIA3KW4CGjs.Entity {
5317
+ var TextEntity = (_class8 = class extends _chunkAGP4VLF4js.Entity {
5005
5318
 
5006
5319
 
5007
5320
 
5008
5321
 
5009
- __init136() {this.nodes = []}
5322
+ __init140() {this.nodes = []}
5010
5323
 
5011
- __init137() {this.fillStyle = "#94a3b8"}
5012
- __init138() {this.strokeStyle = null}
5013
- __init139() {this.hoveredFillStyle = "#ffffff"}
5014
- __init140() {this.lineWidth = 1}
5015
- __init141() {this.isHovered = false}
5324
+ __init141() {this.fillStyle = "#94a3b8"}
5325
+ __init142() {this.strokeStyle = null}
5326
+ __init143() {this.hoveredFillStyle = "#ffffff"}
5327
+ __init144() {this.lineWidth = 1}
5328
+ __init145() {this.isHovered = false}
5016
5329
  constructor(text, atlas, maxWidth, fontSize = 24) {
5017
- super();_class8.prototype.__init136.call(this);_class8.prototype.__init137.call(this);_class8.prototype.__init138.call(this);_class8.prototype.__init139.call(this);_class8.prototype.__init140.call(this);_class8.prototype.__init141.call(this);;
5330
+ super();_class8.prototype.__init140.call(this);_class8.prototype.__init141.call(this);_class8.prototype.__init142.call(this);_class8.prototype.__init143.call(this);_class8.prototype.__init144.call(this);_class8.prototype.__init145.call(this);;
5018
5331
  this.text = text;
5019
5332
  this.atlas = atlas;
5020
5333
  this.fontSize = fontSize;
@@ -5127,17 +5440,17 @@ var TextEntity = (_class8 = class extends _chunkIA3KW4CGjs.Entity {
5127
5440
  }, _class8);
5128
5441
 
5129
5442
  // src/components/GridTextEntity.ts
5130
- var GridTextEntity = (_class9 = class extends _chunkIA3KW4CGjs.Entity {
5443
+ var GridTextEntity = (_class9 = class extends _chunkAGP4VLF4js.Entity {
5131
5444
 
5132
- __init142() {this.fillStyle = "#ffffff"}
5133
- __init143() {this.grid = []}
5445
+ __init146() {this.fillStyle = "#ffffff"}
5446
+ __init147() {this.grid = []}
5134
5447
  // Array of rows
5135
- __init144() {this.cols = 0}
5136
- __init145() {this.rows = 0}
5448
+ __init148() {this.cols = 0}
5449
+ __init149() {this.rows = 0}
5137
5450
 
5138
5451
 
5139
5452
  constructor(_atlas, fontSize = 10) {
5140
- super();_class9.prototype.__init142.call(this);_class9.prototype.__init143.call(this);_class9.prototype.__init144.call(this);_class9.prototype.__init145.call(this);;
5453
+ super();_class9.prototype.__init146.call(this);_class9.prototype.__init147.call(this);_class9.prototype.__init148.call(this);_class9.prototype.__init149.call(this);;
5141
5454
  this.fontSize = fontSize;
5142
5455
  this.charWidth = fontSize * 1;
5143
5456
  this.charHeight = fontSize * 1.1;
@@ -5146,7 +5459,7 @@ var GridTextEntity = (_class9 = class extends _chunkIA3KW4CGjs.Entity {
5146
5459
  updateGrid(ascii) {
5147
5460
  this.grid = ascii;
5148
5461
  this.rows = ascii.length;
5149
- this.cols = _optionalChain([ascii, 'access', _138 => _138[0], 'optionalAccess', _139 => _139.length]) || 0;
5462
+ this.cols = _optionalChain([ascii, 'access', _149 => _149[0], 'optionalAccess', _150 => _150.length]) || 0;
5150
5463
  }
5151
5464
  isPointInside(_globalX, _globalY) {
5152
5465
  return false;
@@ -5221,7 +5534,7 @@ function distSqToSegment(px, py, x1, y1, x2, y2) {
5221
5534
  const ey = py - cy;
5222
5535
  return ex * ex + ey * ey;
5223
5536
  }
5224
- var SplineEntity = (_class10 = class extends _chunkIA3KW4CGjs.Entity {
5537
+ var SplineEntity = (_class10 = class extends _chunkAGP4VLF4js.Entity {
5225
5538
 
5226
5539
 
5227
5540
 
@@ -5229,23 +5542,23 @@ var SplineEntity = (_class10 = class extends _chunkIA3KW4CGjs.Entity {
5229
5542
 
5230
5543
 
5231
5544
 
5232
- __init146() {this.offscreen = null}
5233
- __init147() {this.baked = false}
5545
+ __init150() {this.offscreen = null}
5546
+ __init151() {this.baked = false}
5234
5547
  /** Logical (CSS-pixel) size of the baked bitmap — the blit destination size. */
5235
- __init148() {this.bakedWidth = 0}
5236
- __init149() {this.bakedHeight = 0}
5548
+ __init152() {this.bakedWidth = 0}
5549
+ __init153() {this.bakedHeight = 0}
5237
5550
  /** Gradient strokes can't be baked to a solid-color bitmap; they render per-frame. */
5238
5551
 
5239
5552
  /** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */
5240
- __init150() {this.polylines = null}
5553
+ __init154() {this.polylines = null}
5241
5554
  /**
5242
5555
  * When `true`, the renderer draws a rounded-rect outline of the entity's
5243
5556
  * local bounds after painting the curves. Useful for drag feedback and
5244
5557
  * debugging hit areas. Defaults to `false`.
5245
5558
  */
5246
- __init151() {this.showBounds = false}
5559
+ __init155() {this.showBounds = false}
5247
5560
  constructor(doc, opts = {}) {
5248
- super();_class10.prototype.__init146.call(this);_class10.prototype.__init147.call(this);_class10.prototype.__init148.call(this);_class10.prototype.__init149.call(this);_class10.prototype.__init150.call(this);_class10.prototype.__init151.call(this);;
5561
+ super();_class10.prototype.__init150.call(this);_class10.prototype.__init151.call(this);_class10.prototype.__init152.call(this);_class10.prototype.__init153.call(this);_class10.prototype.__init154.call(this);_class10.prototype.__init155.call(this);;
5249
5562
  this.doc = doc;
5250
5563
  this.lineWidth = _nullishCoalesce(opts.lineWidth, () => ( 2));
5251
5564
  this.cache = _nullishCoalesce(opts.cache, () => ( true));
@@ -5256,7 +5569,7 @@ var SplineEntity = (_class10 = class extends _chunkIA3KW4CGjs.Entity {
5256
5569
  this.width = this.bounds.width;
5257
5570
  this.height = this.bounds.height;
5258
5571
  const isGradient = (c) => c !== null && !Array.isArray(c);
5259
- this.containsGradient = (_nullishCoalesce(_optionalChain([this, 'access', _140 => _140.doc, 'access', _141 => _141.equations, 'optionalAccess', _142 => _142.some, 'call', _143 => _143((eq) => isGradient(eq.color_rgb))]), () => ( false))) || (_nullishCoalesce(_optionalChain([this, 'access', _144 => _144.doc, 'access', _145 => _145.paths, 'optionalAccess', _146 => _146.some, 'call', _147 => _147((p) => isGradient(p.color_rgb))]), () => ( false)));
5572
+ this.containsGradient = (_nullishCoalesce(_optionalChain([this, 'access', _151 => _151.doc, 'access', _152 => _152.equations, 'optionalAccess', _153 => _153.some, 'call', _154 => _154((eq) => isGradient(eq.color_rgb))]), () => ( false))) || (_nullishCoalesce(_optionalChain([this, 'access', _155 => _155.doc, 'access', _156 => _156.paths, 'optionalAccess', _157 => _157.some, 'call', _158 => _158((p) => isGradient(p.color_rgb))]), () => ( false)));
5260
5573
  this.interactive = true;
5261
5574
  }
5262
5575
  computeBounds() {
@@ -5496,7 +5809,7 @@ async function loadSpline(url) {
5496
5809
  }
5497
5810
 
5498
5811
  // src/components/Rect.ts
5499
- var Rect = class extends _chunkIA3KW4CGjs.Entity {
5812
+ var Rect = class extends _chunkAGP4VLF4js.Entity {
5500
5813
 
5501
5814
 
5502
5815
 
@@ -5545,7 +5858,7 @@ var Rect = class extends _chunkIA3KW4CGjs.Entity {
5545
5858
  };
5546
5859
 
5547
5860
  // src/components/Circle.ts
5548
- var Circle = class extends _chunkIA3KW4CGjs.Entity {
5861
+ var Circle = class extends _chunkAGP4VLF4js.Entity {
5549
5862
 
5550
5863
 
5551
5864
 
@@ -5605,7 +5918,7 @@ var Circle = class extends _chunkIA3KW4CGjs.Entity {
5605
5918
  };
5606
5919
 
5607
5920
  // src/components/Group.ts
5608
- var Group = class extends _chunkIA3KW4CGjs.Entity {
5921
+ var Group = class extends _chunkAGP4VLF4js.Entity {
5609
5922
  constructor(...children) {
5610
5923
  super();
5611
5924
  if (children.length > 0) this.add(...children);
@@ -5624,21 +5937,21 @@ var _math = require('@vectojs/math'); _createStarExport(_math);
5624
5937
 
5625
5938
 
5626
5939
  // src/tree/DOMPortalEntity.ts
5627
- var DOMPortalEntity = (_class11 = class extends _chunkIA3KW4CGjs.Entity {
5940
+ var DOMPortalEntity = (_class11 = class extends _chunkAGP4VLF4js.Entity {
5628
5941
 
5629
- __init152() {this.isDOMPortal = true}
5630
- __init153() {this.domListeners = []}
5631
- __init154() {this.resizeObserver = null}
5632
- __init155() {this.domBound = false}
5633
- __init156() {this.cachedWidth = 100}
5634
- __init157() {this.cachedHeight = 100}
5635
- __init158() {this.lastWidth = ""}
5636
- __init159() {this.lastHeight = ""}
5637
- __init160() {this.lastTransform = ""}
5638
- __init161() {this.lastZIndex = ""}
5639
- __init162() {this.lastOpacity = ""}
5942
+ __init156() {this.isDOMPortal = true}
5943
+ __init157() {this.domListeners = []}
5944
+ __init158() {this.resizeObserver = null}
5945
+ __init159() {this.domBound = false}
5946
+ __init160() {this.cachedWidth = 100}
5947
+ __init161() {this.cachedHeight = 100}
5948
+ __init162() {this.lastWidth = ""}
5949
+ __init163() {this.lastHeight = ""}
5950
+ __init164() {this.lastTransform = ""}
5951
+ __init165() {this.lastZIndex = ""}
5952
+ __init166() {this.lastOpacity = ""}
5640
5953
  constructor(domElement, width, height, id) {
5641
- super(id);_class11.prototype.__init152.call(this);_class11.prototype.__init153.call(this);_class11.prototype.__init154.call(this);_class11.prototype.__init155.call(this);_class11.prototype.__init156.call(this);_class11.prototype.__init157.call(this);_class11.prototype.__init158.call(this);_class11.prototype.__init159.call(this);_class11.prototype.__init160.call(this);_class11.prototype.__init161.call(this);_class11.prototype.__init162.call(this);;
5954
+ super(id);_class11.prototype.__init156.call(this);_class11.prototype.__init157.call(this);_class11.prototype.__init158.call(this);_class11.prototype.__init159.call(this);_class11.prototype.__init160.call(this);_class11.prototype.__init161.call(this);_class11.prototype.__init162.call(this);_class11.prototype.__init163.call(this);_class11.prototype.__init164.call(this);_class11.prototype.__init165.call(this);_class11.prototype.__init166.call(this);;
5642
5955
  this.domElement = domElement;
5643
5956
  this.width = _nullishCoalesce(width, () => ( 0));
5644
5957
  this.height = _nullishCoalesce(height, () => ( 0));
@@ -5680,7 +5993,7 @@ var DOMPortalEntity = (_class11 = class extends _chunkIA3KW4CGjs.Entity {
5680
5993
  ];
5681
5994
  for (const type of events) {
5682
5995
  const handler = (e) => {
5683
- this.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)(type, this, e));
5996
+ this.dispatchEvent(new (0, _chunkAGP4VLF4js.VectoJSEvent)(type, this, e));
5684
5997
  };
5685
5998
  this.domElement.addEventListener(type, handler);
5686
5999
  this.domListeners.push({ type, handler, capture: false });
@@ -5691,7 +6004,7 @@ var DOMPortalEntity = (_class11 = class extends _chunkIA3KW4CGjs.Entity {
5691
6004
  ];
5692
6005
  for (const { native, vecto } of hoverEvents) {
5693
6006
  const handler = (e) => {
5694
- this.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)(vecto, this, e, false));
6007
+ this.dispatchEvent(new (0, _chunkAGP4VLF4js.VectoJSEvent)(vecto, this, e, false));
5695
6008
  };
5696
6009
  this.domElement.addEventListener(native, handler);
5697
6010
  this.domListeners.push({ type: native, handler, capture: false });
@@ -5699,7 +6012,7 @@ var DOMPortalEntity = (_class11 = class extends _chunkIA3KW4CGjs.Entity {
5699
6012
  const focusEvents = ["focus", "blur"];
5700
6013
  for (const type of focusEvents) {
5701
6014
  const handler = (e) => {
5702
- this.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)(type, this, e, true));
6015
+ this.dispatchEvent(new (0, _chunkAGP4VLF4js.VectoJSEvent)(type, this, e, true));
5703
6016
  };
5704
6017
  this.domElement.addEventListener(type, handler, true);
5705
6018
  this.domListeners.push({ type, handler, capture: true });
@@ -5750,8 +6063,9 @@ var DOMPortalEntity = (_class11 = class extends _chunkIA3KW4CGjs.Entity {
5750
6063
  }, _class11);
5751
6064
 
5752
6065
  // src/index.ts
5753
- Scene.registerWebGLPointRendererCreator(_chunkL4SWVP2Hjs.createWebGLPointRenderer);
5754
- Scene.registerWebGPUParticleSystemManager(_chunkL4SWVP2Hjs.WebGPUParticleSystemManager);
6066
+ Scene.registerWebGLPointRendererCreator(_chunkGKSCJ6AFjs.createWebGLPointRenderer);
6067
+ Scene.registerWebGPUParticleSystemManager(_chunkGKSCJ6AFjs.WebGPUParticleSystemManager);
6068
+
5755
6069
 
5756
6070
 
5757
6071
 
@@ -5786,4 +6100,4 @@ Scene.registerWebGPUParticleSystemManager(_chunkL4SWVP2Hjs.WebGPUParticleSystemM
5786
6100
 
5787
6101
 
5788
6102
 
5789
- exports.CanvasRenderer = _chunkL4SWVP2Hjs.CanvasRenderer; exports.Circle = Circle; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DOMPortalEntity = DOMPortalEntity; exports.Entity = _chunkIA3KW4CGjs.Entity; exports.GridTextEntity = GridTextEntity; exports.Group = Group; exports.MSDFTextEntity = _chunkIA3KW4CGjs.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.SVGEntity = _chunkIA3KW4CGjs.SVGEntity; exports.SVGRenderer = _chunkL4SWVP2Hjs.SVGRenderer; exports.Scene = Scene; exports.SplineEntity = SplineEntity; exports.TextEntity = TextEntity; exports.TextRasterCache = _chunkL4SWVP2Hjs.TextRasterCache; exports.VectoJSEvent = _chunkIA3KW4CGjs.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkL4SWVP2Hjs.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkL4SWVP2Hjs.createWebGLPointRenderer; exports.isSafeUrl = _chunkL4SWVP2Hjs.isSafeUrl; exports.loadSpline = loadSpline; exports.parseColorToRGBA = _chunkL4SWVP2Hjs.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.sanitizeUrl = _chunkL4SWVP2Hjs.sanitizeUrl;
6103
+ exports.CanvasRenderer = _chunkGKSCJ6AFjs.CanvasRenderer; exports.Circle = Circle; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DOMPortalEntity = DOMPortalEntity; exports.Entity = _chunkAGP4VLF4js.Entity; exports.GlyphRasterAtlas = _chunkGKSCJ6AFjs.GlyphRasterAtlas; exports.GridTextEntity = GridTextEntity; exports.Group = Group; exports.MSDFTextEntity = _chunkAGP4VLF4js.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.SVGEntity = _chunkAGP4VLF4js.SVGEntity; exports.SVGRenderer = _chunkGKSCJ6AFjs.SVGRenderer; exports.Scene = Scene; exports.SplineEntity = SplineEntity; exports.TextEntity = TextEntity; exports.TextRasterCache = _chunkGKSCJ6AFjs.TextRasterCache; exports.VectoJSEvent = _chunkAGP4VLF4js.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkGKSCJ6AFjs.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkGKSCJ6AFjs.createWebGLPointRenderer; exports.isSafeUrl = _chunkGKSCJ6AFjs.isSafeUrl; exports.loadSpline = loadSpline; exports.parseColorToRGBA = _chunkGKSCJ6AFjs.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.sanitizeUrl = _chunkGKSCJ6AFjs.sanitizeUrl;