@pixerate/editor 0.137.0 → 0.139.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/agent/index.cjs +2 -2
- package/dist/agent/index.d.cts +3 -3
- package/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +1 -1
- package/dist/animation-editor/index.cjs +57 -57
- package/dist/animation-editor/index.d.cts +6 -5
- package/dist/animation-editor/index.d.ts +6 -5
- package/dist/animation-editor/index.js +4 -4
- package/dist/{arrange-CKXK_hf7.d.cts → arrange-j_mLiFMO.d.cts} +1 -1
- package/dist/{arrange-BUREmgD9.d.ts → arrange-q5HQvnnB.d.ts} +1 -1
- package/dist/{bezier-Dywv3Bw8.d.ts → bezier-B6cK8SR6.d.ts} +2 -2
- package/dist/{bezier-CZ2AhjID.d.cts → bezier-CEJ8Uw4O.d.cts} +2 -2
- package/dist/{brushTypes-C7N3EBK7.d.cts → brushTypes-MX5UVs_m.d.cts} +27 -2
- package/dist/{brushTypes-C7N3EBK7.d.ts → brushTypes-MX5UVs_m.d.ts} +27 -2
- package/dist/{brushes-DJmUbIgz.d.ts → brushes-CQpL7_Lc.d.ts} +2 -107
- package/dist/{brushes-CnvZSYYU.d.cts → brushes-aEnjqoji.d.cts} +2 -107
- package/dist/{brushstrokeRender-IMFY3FF6.cjs → brushstrokeRender-PP6NTF6M.cjs} +52 -24
- package/dist/brushstrokeRender-PP6NTF6M.cjs.map +1 -0
- package/dist/{brushstrokeRender-2FAXA3XC.js → brushstrokeRender-QGEYJ4OZ.js} +40 -12
- package/dist/brushstrokeRender-QGEYJ4OZ.js.map +1 -0
- package/dist/{builtins-Cc-bH-3U.d.cts → builtins-BakDiKWf.d.cts} +3 -2
- package/dist/{builtins-ZU1GJnUA.d.ts → builtins-CE33vyv2.d.ts} +3 -2
- package/dist/canvas/index.cjs +16 -9
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +60 -14
- package/dist/canvas/index.d.ts +60 -14
- package/dist/canvas/index.js +10 -3
- package/dist/canvas/index.js.map +1 -1
- package/dist/{chunk-PRSRYHQD.cjs → chunk-3QHPFSQM.cjs} +36 -36
- package/dist/{chunk-PRSRYHQD.cjs.map → chunk-3QHPFSQM.cjs.map} +1 -1
- package/dist/{chunk-QBWEH3IM.cjs → chunk-3T5O225T.cjs} +391 -76
- package/dist/chunk-3T5O225T.cjs.map +1 -0
- package/dist/{chunk-EQQMGO42.js → chunk-C2J7GOMJ.js} +18 -5
- package/dist/chunk-C2J7GOMJ.js.map +1 -0
- package/dist/{chunk-2XS6LTUP.cjs → chunk-C46AZEBE.cjs} +104 -339
- package/dist/chunk-C46AZEBE.cjs.map +1 -0
- package/dist/{chunk-HUO3TI4C.js → chunk-CPSMARTX.js} +6 -241
- package/dist/chunk-CPSMARTX.js.map +1 -0
- package/dist/{chunk-KEENUAFN.cjs → chunk-DLLUOVQX.cjs} +18 -5
- package/dist/chunk-DLLUOVQX.cjs.map +1 -0
- package/dist/{chunk-LSXXAJG3.js → chunk-J7DSKTKB.js} +5 -4
- package/dist/chunk-J7DSKTKB.js.map +1 -0
- package/dist/{chunk-6S6362D2.js → chunk-NI6RORDS.js} +2 -2
- package/dist/{chunk-XN2NBLV5.cjs → chunk-PMEHFHSP.cjs} +82 -82
- package/dist/{chunk-XN2NBLV5.cjs.map → chunk-PMEHFHSP.cjs.map} +1 -1
- package/dist/{chunk-5FSRTP6E.js → chunk-PQFVLOMR.js} +319 -4
- package/dist/chunk-PQFVLOMR.js.map +1 -0
- package/dist/{chunk-OIBIRXRG.js → chunk-PSPLRQL6.js} +2 -2
- package/dist/{chunk-SYT2TLQF.js → chunk-QEIN6JQO.js} +2 -2
- package/dist/{chunk-3D77LOBM.js → chunk-TJBYBSTN.js} +51 -6
- package/dist/chunk-TJBYBSTN.js.map +1 -0
- package/dist/{chunk-DFSZO2B5.cjs → chunk-UFL5N4DK.cjs} +5 -4
- package/dist/chunk-UFL5N4DK.cjs.map +1 -0
- package/dist/{chunk-TLCTTQFF.cjs → chunk-VEHVXRJU.cjs} +17 -17
- package/dist/{chunk-TLCTTQFF.cjs.map → chunk-VEHVXRJU.cjs.map} +1 -1
- package/dist/{chunk-5OOIUXNJ.cjs → chunk-XFXHA5MU.cjs} +55 -10
- package/dist/chunk-XFXHA5MU.cjs.map +1 -0
- package/dist/commands/index.cjs +2 -2
- package/dist/commands/index.d.cts +3 -3
- package/dist/commands/index.d.ts +3 -3
- package/dist/commands/index.js +1 -1
- package/dist/effects/index.d.cts +5 -4
- package/dist/effects/index.d.ts +5 -4
- package/dist/{export-DwnZ_hvQ.d.ts → export-0ZaTK__4.d.ts} +4 -4
- package/dist/{export-41rL6Cne.d.ts → export-C45v6MYq.d.ts} +1 -1
- package/dist/{export-CyJdfP_Z.d.cts → export-D-7WCDJ9.d.cts} +4 -4
- package/dist/{export-D9mXp05Q.d.cts → export-DSdCfd6O.d.cts} +1 -1
- package/dist/grain-BWmGZxP4.d.cts +58 -0
- package/dist/grain-BWmGZxP4.d.ts +58 -0
- package/dist/{helpers-v6GMazzg.d.cts → helpers-Cavy1TB7.d.cts} +21 -1
- package/dist/{helpers-C-RgeyvH.d.ts → helpers-LZQ4cpg4.d.ts} +21 -1
- package/dist/image-editor/index.cjs +695 -677
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +12 -11
- package/dist/image-editor/index.d.ts +12 -11
- package/dist/image-editor/index.js +23 -5
- package/dist/image-editor/index.js.map +1 -1
- package/dist/{opentype-KR_jj6b_.d.cts → opentype-BpIJhGpj.d.cts} +1 -1
- package/dist/{opentype-a1KAW-qs.d.ts → opentype-CyxDScVz.d.ts} +1 -1
- package/dist/paint/index.cjs +21 -3
- package/dist/paint/index.cjs.map +1 -1
- package/dist/paint/index.d.cts +72 -5
- package/dist/paint/index.d.ts +72 -5
- package/dist/paint/index.js +20 -2
- package/dist/paint-editor/index.cjs +171 -171
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +77 -29
- package/dist/paint-editor/index.d.ts +77 -29
- package/dist/paint-editor/index.js +12 -12
- package/dist/{pathfinder-BHS4vQNm.d.cts → pathfinder-BMQEcS0k.d.cts} +2 -2
- package/dist/{pathfinder-BVvTIBMo.d.ts → pathfinder-CQIxubuP.d.ts} +2 -2
- package/dist/{pool-CZQOrSXZ.d.ts → pool-Cynuj89K.d.ts} +1 -1
- package/dist/{pool-FjZZmZ80.d.cts → pool-DedjxDKo.d.cts} +1 -1
- package/dist/{rand-8xbUb6ur.d.ts → rand-COS4_E1g.d.ts} +17 -1
- package/dist/{rand-Cw9VLrVC.d.cts → rand-D1JqpR-p.d.cts} +17 -1
- package/dist/{svg-DTb40ICU.d.cts → svg-RcixtBFR.d.cts} +2 -2
- package/dist/{svg-CSz3mzkw.d.ts → svg-nWBJEpnk.d.ts} +2 -2
- package/dist/{tools-Ca0kI1qo.d.cts → tools-CL82d7nw.d.cts} +1 -1
- package/dist/{tools-B0wXP5cF.d.ts → tools-DrxgDnXY.d.ts} +1 -1
- package/dist/vector/index.cjs +995 -955
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +44 -18
- package/dist/vector/index.d.ts +44 -18
- package/dist/vector/index.js +55 -15
- package/dist/vector/index.js.map +1 -1
- package/dist/video-editor/index.d.cts +5 -4
- package/dist/video-editor/index.d.ts +5 -4
- package/dist/{warp-DTOp0lON.d.cts → warp-CHi_P_G6.d.cts} +1 -1
- package/dist/{warp-pd7hrH4T.d.ts → warp-CPaHULUL.d.ts} +1 -1
- package/package.json +1 -1
- package/skills/pixerate-canvas/SKILL.md +12 -5
- package/skills/pixerate-vector/SKILL.md +4 -2
- package/dist/brushstrokeRender-2FAXA3XC.js.map +0 -1
- package/dist/brushstrokeRender-IMFY3FF6.cjs.map +0 -1
- package/dist/chunk-2XS6LTUP.cjs.map +0 -1
- package/dist/chunk-3D77LOBM.js.map +0 -1
- package/dist/chunk-5FSRTP6E.js.map +0 -1
- package/dist/chunk-5OOIUXNJ.cjs.map +0 -1
- package/dist/chunk-DFSZO2B5.cjs.map +0 -1
- package/dist/chunk-EQQMGO42.js.map +0 -1
- package/dist/chunk-HUO3TI4C.js.map +0 -1
- package/dist/chunk-KEENUAFN.cjs.map +0 -1
- package/dist/chunk-LSXXAJG3.js.map +0 -1
- package/dist/chunk-QBWEH3IM.cjs.map +0 -1
- /package/dist/{chunk-6S6362D2.js.map → chunk-NI6RORDS.js.map} +0 -0
- /package/dist/{chunk-OIBIRXRG.js.map → chunk-PSPLRQL6.js.map} +0 -0
- /package/dist/{chunk-SYT2TLQF.js.map → chunk-QEIN6JQO.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/canvas/index.cjs","../../src/canvas/trajectory.ts","../../src/canvas/displacement.ts","../../src/canvas/clipboard.ts","../../src/canvas/view.ts","../../src/canvas/streaming.ts","../../src/canvas/arrange.ts","../../src/canvas/guides.ts","../../src/canvas/connectors.ts","../../src/canvas/scenes.ts","../../src/canvas/shapeNodes.ts","../../src/canvas/resizeSnap.ts","../../src/canvas/autoLayout.ts","../../src/canvas/arrangeController.ts","../../src/canvas/measure.ts","../../src/canvas/autoLayoutController.ts","../../src/canvas/tokenBindings.ts","../../src/canvas/animation/model.ts","../../src/canvas/animation/evaluate.ts","../../src/canvas/animation/edit.ts","../../src/canvas/animation/export.ts","../../src/canvas/animation/timeline.ts","../../src/canvas/animation/controller.ts","../../src/canvas/components.ts","../../src/canvas/componentsController.ts","../../src/canvas/freehand.ts","../../src/canvas/drawController.ts","../../src/canvas/mermaid/layout.ts","../../src/canvas/mermaid/toFlow.ts","../../src/canvas/mermaid/view.ts","../../src/canvas/mermaid/stream.ts","../../src/canvas/mermaid/theme.ts","../../src/canvas/mermaid/scene-kit.ts","../../src/canvas/mermaid/families/pie.ts","../../src/canvas/mermaid/families/charts.ts","../../src/canvas/mermaid/families/sequence.ts","../../src/canvas/mermaid/families/journey.ts","../../src/canvas/mermaid/families/quadrant.ts","../../src/canvas/mermaid/families/gitgraph.ts","../../src/canvas/mermaid/families/mindmap.ts","../../src/canvas/mermaid/families/c4.ts","../../src/canvas/mermaid/families/architecture.ts","../../src/canvas/mermaid/families/gantt.ts","../../src/canvas/mermaid/families/timeline.ts","../../src/canvas/mermaid/families/index.ts","../../src/canvas/mermaid/scene.ts","../../src/canvas/mermaid/shapes.ts","../../src/canvas/mermaid/export.ts","../../src/canvas/mermaid/index.ts","../../src/canvas/agent-presence.ts","../../src/canvas/capture.ts","../../src/canvas/effects.ts","../../src/canvas/effect-player.ts","../../src/canvas/effect-tools.ts"],"names":["n","next","size","best","inside","scenes","toIndex","defaultId","done","rect","layout","sizeOf","lim","depth","px","EPS","A","text","camel","stringRecord","measured","out","isText","old","root","after","matches","nodes","target","info","set","finite","dagre","num","positions","clampHeight","NODES_PER_RANK","mix","document","estimateTextWidth","TITLE_SIZE","line","M","round","place","r2","LINE_HEIGHT","titleMark","colours","ellipse","boxOf","arc"],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACF,yDAA8B;AAC9B;AACE;AACA;AACF,yDAA8B;AAC9B;AACE;AACF,yDAA8B;AAC9B;AACE;AACF,yDAA8B;AAC9B,iCAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACF,yDAA8B;AAC9B,iCAA8B;AAC9B;AACE;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;A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ACF,MAAA;AACL,IAAA;AACD,EAAA;AACI,EAAA;AACEC,EAAAA;AACD,IAAA;AACJ,IAAA;AACA,IAAA;AACO,IAAA;AACC,IAAA;AACT,EAAA;AACI,EAAA;AACGA,EAAAA;AACR;AAiBS;AAMF,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACF,EAAA;AACF,EAAA;AACA,EAAA;AACC,IAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACA,EAAA;AACK,IAAA;AACL,EAAA;AACA,EAAA;AACK,IAAA;AAAyC,IAAA;AAE9C,EAAA;AACM,EAAA;AACJ,IAAA;AACA,IAAA;AACE,IAAA;AACA,EAAA;AACE,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACG,IAAA;AACA,EAAA;AACE,EAAA;AACF,IAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACQ,IAAA;AACT,EAAA;AACI,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAES;AACD,EAAA;AACR;AAOgB;AAKT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACJ,EAAA;AACG,EAAA;AACG,EAAA;AACR;AAMgB;AAKT,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACH,IAAA;AACD,IAAA;AACJ,EAAA;AACM,EAAA;AACR;AAiFM;AAGU;AACV,EAAA;AACC,EAAA;AACF,EAAA;AACA,EAAA;AACE,EAAA;AACC,EAAA;AACR;AAES;AACW,EAAA;AACT,EAAA;AACN,EAAA;AACI,EAAA;AACT;AAES;AACF,EAAA;AACC,EAAA;AACR;AAGgB;AAIT,EAAA;AACF,EAAA;AACA,EAAA;AACK,EAAA;AACV;AAGgB;AAIT,EAAA;AACF,EAAA;AACA,EAAA;AACE,EAAA;AACF,EAAA;AACK,EAAA;AACV;AAES;AACF,EAAA;AACC,EAAA;AACN,IAAA;AACA,IAAA;AACD,EAAA;AACD;AAES;AAIJ,EAAA;AACG,EAAA;AACF,IAAA;AACJ,IAAA;AACA,IAAA;AACO,IAAA;AACP,IAAA;AACD,EAAA;AACD;AASgB;AAMT,EAAA;AACD,EAAA;AACC,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACK,IAAA;AACF,MAAA;AACH,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACI,IAAA;AACL,IAAA;AACM,IAAA;AACN,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAUgB;AAKT,EAAA;AACF,EAAA;AACI,IAAA;AACR,EAAA;AACM,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACL,MAAA;AACM,MAAA;AACD,MAAA;AACL,MAAA;AACM,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACK,IAAA;AACF,MAAA;AACH,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACI,IAAA;AACL,IAAA;AACM,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACI,QAAA;AACH,QAAA;AACA,QAAA;AACS,MAAA;AACX,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAQgB;AAKT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACF,IAAA;AACC,MAAA;AACJ,MAAA;AACD,IAAA;AACI,IAAA;AACA,IAAA;AACL,EAAA;AACO,EAAA;AACR;AVi6CY;AACA;AWtnEC;AACZ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAGgB;AACL,EAAA;AACA,EAAA;AACD,EAAA;AACV;AAMgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACA,EAAA;AACD,EAAA;AACD,EAAA;AACT;AAkCS;AACA,EAAA;AACC,EAAA;AACA,EAAA;AACF,EAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AACM,EAAA;AACG,EAAA;AACF,EAAA;AACR;AAES;AACA,EAAA;AACF,EAAA;AACG,EAAA;AACF,EAAA;AACR;AAegB;AAMT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACAC,EAAAA;AAEA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAGA,EAAA;AACC,IAAA;AACF,IAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACCP,MAAAA;AACN,MAAA;AACD,IAAA;AACI,IAAA;AACE,IAAA;AACL,MAAA;AACK,QAAA;AACA,QAAA;AACL,MAAA;AACD,IAAA;AACI,IAAA;AACA,IAAA;AACC,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACO,IAAA;AACR,EAAA;AAEU,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACJ,EAAA;AACC,IAAA;AACF,IAAA;AACC,MAAA;AACC,MAAA;AACN,IAAA;AACK,IAAA;AACN,EAAA;AAEI,EAAA;AACA,EAAA;AACE,EAAA;AAEF,EAAA;AACC,IAAA;AAEG,MAAA;AAEA,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACI,QAAA;AACC,QAAA;AACL,QAAA;AACD,MAAA;AACC,QAAA;AACA,QAAA;AACI,QAAA;AACC,QAAA;AACL,QAAA;AACD,MAAA;AACM,MAAA;AACF,MAAA;AACH,QAAA;AACD,MAAA;AACD,IAAA;AACC,MAAA;AACM,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACP,IAAA;AACC,MAAA;AACM,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACP,IAAA;AACM,EAAA;AACF,IAAA;AACA,IAAA;AACL,EAAA;AAEM,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACC,IAAA;AAEA,IAAA;AACA,IAAA;AACC,IAAA;AACN,MAAA;AAGD,IAAA;AACD,EAAA;AACS,EAAA;AACA,EAAA;AACH,EAAA;AACC,EAAA;AACA,IAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACD;AAiBgB;AAOT,EAAA;AACA,EAAA;AACD,EAAA;AACG,IAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACC,EAAA;AACH,IAAA;AACH,IAAA;AACC,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAMgB;AAKP,EAAA;AACFA,EAAAA;AACG,EAAA;AACV;AXihEY;AACA;AY10EC;AAEA;AAoHC;AACF;AACN;AACA;AAGU;AACP,EAAA;AACJ,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAEM;AAGU;AACTQ,EAAAA;AACCA,EAAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACT,EAAA;AACC,EAAA;AACR;AAGgB;AACT,EAAA;AACC,EAAA;AACR;AAES;AACF,EAAA;AACAA,EAAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAGgB;AACX,EAAA;AACA,EAAA;AACK,EAAA;AACA,IAAA;AACT,EAAA;AACK,EAAA;AACE,EAAA;AACR;AAcM;AACY,kBAAA;AACR,EAAA;AACQ,kBAAA;AACA,mBAAA;AAEjB,EAAA;AACM,IAAA;AACL,IAAA;AACOR,MAAAA;AACA,MAAA;AACD,QAAA;AACJ,QAAA;AACA,QAAA;AACG,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACH,QAAA;AACD,MAAA;AACK,MAAA;AACA,MAAA;AACN,IAAA;AACA,IAAA;AACK,MAAA;AACL,IAAA;AACD,EAAA;AAE8B,EAAA;AACvB,IAAA;AACN,IAAA;AACO,MAAA;AACA,MAAA;AACF,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACL,IAAA;AACO,I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AA;AAGM,EAAA;AACF,EAAA;AACE,EAAA;AACG,EAAA;AACA,EAAA;AACL,EAAA;AACG,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AACI,EAAA;AACG,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AACU,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AAGF,EAAA;AACE,EAAA;AACI,EAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACH,MAAA;AACM,IAAA;AACF,MAAA;AACE,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACC,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACM,EAAA;AACE,IAAA;AACD,IAAA;AACA,IAAA;AACD,IAAA;AACE,IAAA;AACP,IAAA;AACO,MAAA;AACA,MAAA;AACF,MAAA;AACA,MAAA;AAAoC,MAAA;AAC0B,MAAA;AAG9D,MAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AACA,IAAA;AACA,EAAA;AACF;AAUS;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACG,IAAA;AACF,IAAA;AACL,EAAA;AACI,EAAA;AACK,EAAA;AACV;AAES;AACE,EAAA;AACJX,EAAAA;AACG,EAAA;AACV;AAES;AACE,EAAA;AACJ,EAAA;AACG,EAAA;AACV;AAoBS;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAEM;AAIG;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACL,IAAA;AACO,MAAA;AACD,MAAA;AACL,MAAA;AACD,IAAA;AACO,IAAA;AACR,EAAA;AACM,EAAA;AACE,IAAA;AACP,IAAA;AACD,EAAA;AACM,EAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACD,MAAA;AACA,MAAA;AACL,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACM,EAAA;AACD,EAAA;AACI,IAAA;AACJ,IAAA;AACE,MAAA;AACL,MAAA;AACD,IAAA;AACA,EAAA;AACG,EAAA;AACC,EAAA;AACA,IAAA;AACI,IAAA;AACF,IAAA;AACE,IAAA;AACA,IAAA;AACJ,IAAA;AACC,MAAA;AACJ,MAAA;AAAuE,QAAA;AAAI,QAAA;AAAO,MAAA;AAC9E,MAAA;AACL,IAAA;AACK,MAAA;AACJ,MAAA;AAAyE,QAAA;AAAI,QAAA;AAAO,MAAA;AAC9E,IAAA;AACD,MAAA;AACD,MAAA;AACJ,MAAA;AACK,QAAA;AACH,UAAA;AACA,UAAA;AACD,QAAA;AACA,QAAA;AACI,QAAA;AACH,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACK,MAAA;AACL,MAAA;AACD,IAAA;AACQ,IAAA;AACA,IAAA;AACR,EAAA;AACK,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAOS;AACF,EAAA;AACC,IAAA;AACC,IAAA;AACP,EAAA;AACG,EAAA;AACE,EAAA;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACQ,IAAA;AACD,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAGS;AASF,EAAA;AACA,EAAA;AACC,IAAA;AACC,IAAA;AACP,EAAA;AACK,EAAA;AACA,EAAA;AACE,IAAA;AACH,IAAA;AACJ,EAAA;AACK,EAAA;AACE,IAAA;AACH,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACD,IAAA;AACA,EAAA;AACM,EAAA;AACR;AAES;AACA,EAAA;AACD,EAAA;AACA,IAAA;AACDE,IAAAA;AACE,IAAA;AACP,EAAA;AACF;AAES;AACE,EAAA;AACJ,EAAA;AACI,EAAA;AACH,EAAA;AACD,EAAA;AACE,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACAU,IAAAA;AACE,IAAA;AACA,IAAA;AACT,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACN,IAAA;AACM,IAAA;AACN,IAAA;AACO,IAAA;AACC,IAAA;AACT,EAAA;AACD;AAES;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACP,EAAA;AACC,IAAA;AACA,IAAA;AACO,IAAA;AACA,IAAA;AACR,EAAA;AACQ,EAAA;AACA,EAAA;AAEJ,EAAA;AACJ,EAAA;AACO,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACI,EAAA;AACI,EAAA;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACH,MAAA;AACC,QAAA;AACE,QAAA;AACF,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACM,IAAA;AACA,IAAA;AACA,IAAA;AACP,EAAA;AACD;AASS;AACE,EAAA;AACF,EAAA;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACH,EAAA;AACA,EAAA;AACA,EAAA;AACE,EAAA;AACP,IAAA;AACO,MAAA;AACA,MAAA;AACF,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACE,MAAA;AACA,MAAA;AACN,MAAA;AACC,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACF,MAAA;AACA,MAAA;AACC,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAA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AAA;AACM,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACF,IAAA;AACE,IAAA;AACF,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACV,EAAA;AACD,EAAA;AACE,EAAA;AACF,EAAA;AACJ,EAAA;AACO,EAAA;AACR;AAMgB;AAMX,EAAA;AACG,IAAA;AACAI,IAAAA;AAEA,IAAA;AACF,IAAA;AACJ,IAAA;AACK,MAAA;AAAqD,MAAA;AAExD,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACK,IAAA;AACE,IAAA;AACR,EAAA;AACM,EAAA;AACF,EAAA;AACM,EAAA;AACH,EAAA;AACR;AAEY;AACP,EAAA;AACA,EAAA;AACGjB,IAAAA;AACC,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACA,EAAA;AACF,EAAA;AACK,EAAA;AACV;AAES;AACF,EAAA;AACC,EAAA;AACR;AAES;AACF,EAAA;AACI,EAAA;AACD,EAAA;AACV;AAEM;AAEG;AACD,EAAA;AACD,IAAA;AACE,MAAA;AACF,MAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACM,IAAA;AACC,MAAA;AACF,MAAA;AACA,MAAA;AACA,MAAA;AACH,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAES;AACD,EAAA;AAGR;AAES;AACD,EAAA;AACN,IAAA;AACA,IAAA;AACM,IAAA;AACC,IAAA;AACP,IAAA;AACO,IAAA;AACAU,MAAAA;AACN,MAAA;AACD,IAAA;AACQ,IAAA;AACDA,MAAAA;AACDA,MAAAA;AACL,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAGsB;AAEb;AACD,EAAA;AACN,IAAA;AACA,IAAA;AACCA,IAAAA;AACM,MAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AACCA,IAAAA;AACM,MAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAGa;AACZ,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACE,IAAA;AACR,IAAA;AACG,IAAA;AACJ,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACP,IAAA;AACG,IAAA;AACJ,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACP,IAAA;AACG,IAAA;AACJ,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACP,IAAA;AAAiB;AAEV,IAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AACQ,IAAA;AACT,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACP,IAAA;AACO,MAAA;AACN,MAAA;AACD,IAAA;AACO,IAAA;AACA,MAAA;AACF,MAAA;AACJ,MAAA;AACD,IAAA;AACQ,IAAA;AACD,MAAA;AACN,MAAA;AACD,IAAA;AACD,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACP,IAAA;AACO,IAAA;AACC,IAAA;AACT,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACP,IAAA;AACO,IAAA;AACC,IAAA;AACT,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACN,IAAA;AACO,IAAA;AACC,IAAA;AACT,EAAA;AACD;AASgD;AAEvC;AACJ,EAAA;AACE,EAAA;AACN,EAAA;AACO,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAEgB;AACP,EAAA;AACT;AAGgB;AACP,EAAA;AACT;AAGgB;AACR,EAAA;AACR;AAGgB;AACT,EAAA;AACC,EAAA;AACR;AAES;AACD,EAAA;AACR;AAES;AACR,EAAA;AACK,IAAA;AACG,MAAA;AAED,MAAA;AACN,IAAA;AACK,IAAA;AACN,EAAA;AACD;AAMgB;AAMT,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACH,IAAA;AACC,IAAA;AACP,EAAA;AACM,EAAA;AACF,EAAA;AACK,EAAA;AACF,EAAA;AACR;AAES;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACA,EAAA;AACC,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACG,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACM,IAAA;AACF,IAAA;AACA,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACK,IAAA;AACE,IAAA;AACR,EAAA;AACS,EAAA;AACV;AAES;AACF,EAAA;AACF,EAAA;AACJ,EAAA;AACK,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACD,IAAA;AACE,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAOgB;AACP,EAAA;AACJ,EAAA;AACE,EAAA;AACD,IAAA;AACE,IAAA;AACF,IAAA;AACG,IAAA;AACP,EAAA;AACM,EAAA;AACR;AAUgB;AAMT,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACD,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACF,EAAA;AACM,EAAA;AACD,EAAA;AACV;AAQgB;AAQT,EAAA;AACD,EAAA;AACD,EAAA;AACG,IAAA;AACD,IAAA;AACD,IAAA;AACG,MAAA;AACP,IAAA;AACD,EAAA;AACM,EAAA;AACF,EAAA;AACE,EAAA;AACA,IAAA;AACC,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACM,IAAA;AACF,MAAA;AACJ,MAAA;AACD,IAAA;AACA,IAAA;AACO,IAAA;AACP,EAAA;AACM,EAAA;AACR;AAMgB;AAQX,EAAA;AACG,IAAA;AACD,IAAA;AACA,IAAA;AACN,EAAA;AACM,EAAA;AACF,EAAA;AACE,EAAA;AACA,IAAA;AACC,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACM,IAAA;AACF,MAAA;AACE,MAAA;AACP,IAAA;AACA,IAAA;AACO,IAAA;AACP,EAAA;AACM,EAAA;AACR;AAQgB;AAMT,EAAA;AACA,EAAA;AACE,EAAA;AACJ,EAAA;AACE,EAAA;AACC,IAAA;AACA,IAAA;AACF,IAAA;AACA,IAAA;AACE,IAAA;AACN,IAAA;AACO,IAAA;AACP,IAAA;AACO,IAAA;AACP,EAAA;AACM,EAAA;AACR;AAMgB;AACX,EAAA;AACE,EAAA;AACC,IAAA;AACA,IAAA;AACN,IAAA;AACI,IAAA;AACJ,IAAA;AACO,IAAA;AACP,EAAA;AACM,EAAA;AACR;AhBy1HY;AACA;AiBr7IC;AAgCA;AACR,EAAA;AACA,EAAA;AACGV,IAAAA;AACC,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAEc;AAEL;AACD,EAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACG,IAAA;AACI,IAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AACQ,IAAA;AACD,MAAA;AACN,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAES;AACD,EAAA;AACN,IAAA;AACA,IAAA;AACM,IAAA;AACD,IAAA;AACC,IAAA;AACC,IAAA;AACC,IAAA;AACD,MAAA;AACN,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAES;AACF,EAAA;AACD,EAAA;AACD,EAAA;AACG,IAAA;AACC,IAAA;AACA,EAAA;AACA,IAAA;AACR,EAAA;AACD;AASa;AACZ,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACA,IAAA;AACC,IAAA;AACC,IAAA;AACT,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACA,IAAA;AACC,IAAA;AACC,IAAA;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACA,IAAA;AACC,IAAA;AACC,IAAA;AACJ,MAAA;AACH,MAAA;AACD,IAAA;AACD,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACD,IAAA;AACE,IAAA;AACC,IAAA;AACJ,MAAA;AACH,MAAA;AACD,IAAA;AACD,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACD,IAAA;AACA,IAAA;AACE,IAAA;AACC,IAAA;AACJ,MAAA;AACH,MAAA;AACD,IAAA;AACD,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACD,IAAA;AACA,IAAA;AACC,IAAA;AACE,IAAA;AACD,MAAA;AACD,MAAA;AACC,MAAA;AACF,MAAA;AACA,QAAA;AACH,QAAA;AACD,MAAA;AACI,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAOgB;AACP,EAAA;AACT;AAGgB;AACV,EAAA;AACG,EAAA;AACJ,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAGgB;AACT,EAAA;AACF,EAAA;AACG,EAAA;AACR;AAGgB;AACT,EAAA;AACC,EAAA;AACR;AAGgB;AACT,EAAA;AACD,EAAA;AACE,EAAA;AACR;AAGgB;AACT,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAMgB;AACT,EAAA;AACF,EAAA;AACH,IAAA;AACK,MAAA;AACL,IAAA;AACD,EAAA;AACM,EAAA;AACF,EAAA;AACM,EAAA;AACD,EAAA;AACV;AjBu3IY;AACA;AkBrmJN;AAGU;AACL,EAAA;AACV,EAAA;AACO,IAAA;AACD,IAAA;AACL,IAAA;AACM,MAAA;AACC,MAAA;AACF,MAAA;AACL,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAWgB;AACT,EAAA;AACA,EAAA;AACN,EAAA;AACK,IAAA;AACE,IAAA;AACD,IAAA;AACD,IAAA;AACJ,IAAA;AACM,MAAA;AACC,MAAA;AACA,MAAA;AACF,MAAA;AACH,MAAA;AACF,IAAA;AACI,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAMgB;AACT,EAAA;AACF,EAAA;AACE,EAAA;AACC,IAAA;AACD,IAAA;AACD,IAAA;AACJ,IAAA;AACO,MAAA;AACN,MAAA;AACD,IAAA;AACI,IAAA;AACG,IAAA;AACP,EAAA;AACM,EAAA;AACR;AAMgB;AACR,EAAA;AACR;AlBilJY;AACA;AmBroJC;AACH,EAAA;AACT,EAAA;AACO,IAAA;AACD,IAAA;AACA,IAAA;AACN,EAAA;AACD;AAGa;AAEP;AACA;AAGU;AACT,EAAA;AACA,EAAA;AAEC,EAAA;AACR;AAES;AACF,EAAA;AACF,EAAA;AACG,IAAA;AACF,IAAA;AACG,IAAA;AACR,EAAA;AACMA,EAAAA;AACF,EAAA;AACG,EAAA;AACR;AAES;AACJ,EAAA;AACG,IAAA;AACF,IAAA;AACL,EAAA;AACU,EAAA;AACX;AAES;AACJ,EAAA;AACG,EAAA;AACR;AAMA;AAKC,EAAA;AAA6B,IAAA;AAAkC,IAAA;AACzD,IAAA;AACN,EAAA;AAF6B,EAAA;AAAkC,EAAA;AAJ9C,EAAA;AACA,mBAAA;AACA,mBAAA;AAMO,EAAA;AACjB,IAAA;AACD,IAAA;AACE,IAAA;AACR,EAAA;AAEyB,EAAA;AACjB,IAAA;AACR,EAAA;AAEU,EAAA;AACL,IAAA;AACC,IAAA;AACE,MAAA;AACA,MAAA;AACN,MAAA;AACK,QAAA;AACL,MAAA;AACA,MAAA;AACK,MAAA;AACN,IAAA;AACO,IAAA;AACR,EAAA;AAEiB,EAAA;AACT,IAAA;AACR,EAAA;AAEkB,EAAA;AACV,IAAA;AACR,EAAA;AAEQ,EAAA;AACD,IAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AAEA,EAAA;AACO,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AAEU,EAAA;AACJ,IAAA;AACN,EAAA;AAE+B,EAAA;AACxB,IAAA;AACC,IAAA;AACR,EAAA;AAES,EAAA;AACD,IAAA;AACR,EAAA;AAEkB,EAAA;AACb,IAAA;AACG,IAAA;AACA,MAAA;AACA,MAAA;AACD,MAAA;AACC,MAAA;AACN,MAAA;AACA,IAAA;AACF,EAAA;AACD;AAES;AACH,EAAA;AACI,EAAA;AACH,EAAA;AACN,EAAA;AACM,IAAA;AACE,MAAA;AACP,IAAA;AACM,IAAA;AACF,IAAA;AACE,IAAA;AACF,IAAA;AACE,IAAA;AACD,IAAA;AACN,EAAA;AACD;AAGgB;AACT,EAAA;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAGgB;AACT,EAAA;AACA,EAAA;AACF,EAAA;AACG,EAAA;AACR;AAOS;AACF,EAAA;AACA,EAAA;AACI,EAAA;AACF,IAAA;AACD,EAAA;AAEA,IAAA;AACA,IAAA;AACD,IAAA;AACN,EAAA;AACM,EAAA;AACP;AAGgB;AAQT,EAAA;AACA,EAAA;AACI,EAAA;AACF,EAAA;AACD,EAAA;AACA,EAAA;AACR;AAOgB;AAMT,EAAA;AACI,EAAA;AACJ,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACN,IAAA;AACO,MAAA;AACD,MAAA;AACC,MAAA;AACF,MAAA;AACJ,MAAA;AACK,MAAA;AACN,IAAA;AACD,EAAA;AACS,EAAA;AACV;AASgB;AACT,EAAA;AACN,EAAA;AACM,EAAA;AACI,EAAA;AACJ,EAAA;AACI,EAAA;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACL,EAAA;AAEC,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAGgB;AACT,EAAA;AACN,EAAA;AACM,EAAA;AACN,EAAA;AACO,IAAA;AACA,MAAA;AACC,MAAA;AACN,MAAA;AACA,IAAA;AACK,IAAA;AACP,EAAA;AACO,EAAA;AACR;AAMgB;AACT,EAAA;AACN,EAAA;AACM,EAAA;AACN,EAAA;AACA,EAAA;AACS,IAAA;AACF,IAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACA,MAAA;AACF,MAAA;AACL,IAAA;AACM,IAAA;AACP,EAAA;AACO,EAAA;AACR;AAGgB;AACT,EAAA;AACD,EAAA;AACG,EAAA;AACF,EAAA;AACC,EAAA;AACR;AAMgB;AACT,EAAA;AACA,EAAA;AACD,EAAA;AACK,EAAA;AACN,EAAA;AACG,IAAA;AACF,IAAA;AACL,EAAA;AACM,EAAA;AACF,EAAA;AACG,EAAA;AACR;AAGgB;AACT,EAAA;AACA,EAAA;AACF,EAAA;AACG,IAAA;AACP,EAAA;AACM,EAAA;AACC,EAAA;AACR;AAwBgB;AAMX,EAAA;AACE,EAAA;AACN,EAAA;AACM,EAAA;AACA,EAAA;AACI,EAAA;AACN,EAAA;AACJ,EAAA;AACO,IAAA;AACA,IAAA;AACC,IAAA;AACR,EAAA;AACU,EAAA;AACJ,EAAA;AACA,EAAA;AACG,EAAA;AACF,EAAA;AACE,EAAA;AACV;AAGS;AACF,EAAA;AACN,EAAA;AACA,EAAA;AACS,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACP,EAAA;AACD;AAWgB;AAMX,EAAA;AACE,EAAA;AACN,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACJ,EAAA;AACS,IAAA;AACA,IAAA;AACT,EAAA;AACU,EAAA;AACJ,EAAA;AACG,EAAA;AACF,EAAA;AACE,EAAA;AACV;AAGgB;AACT,EAAA;AACA,EAAA;AACN,EAAA;AACM,EAAA;AACN,EAAA;AACK,IAAA;AACE,IAAA;AACD,IAAA;AACL,IAAA;AACO,MAAA;AACN,MAAA;AACD,IAAA;AACD,EAAA;AACM,EAAA;AACC,EAAA;AACR;AAcgB;AACT,EAAA;AACF,EAAA;AACJ,EAAA;AACO,IAAA;AACF,IAAA;AACA,IAAA;AACG,IAAA;AACA,MAAA;AACF,MAAA;AACC,MAAA;AACN,IAAA;AACA,IAAA;AACO,MAAA;AACF,MAAA;AACE,MAAA;AACF,MAAA;AACL,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAagB;AACT,EAAA;AACN,EAAA;AACM,EAAA;AACA,EAAA;AACC,EAAA;AACA,IAAA;AACN,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACI,MAAA;AACH,IAAA;AACH,EAAA;AACD;AAGgB;AACX,EAAA;AACA,EAAA;AACC,IAAA;AACA,IAAA;AACH,MAAA;AACO,IAAA;AACP,MAAA;AACD,IAAA;AACD,EAAA;AACI,EAAA;AACE,EAAA;AACG,EAAA;AACH,EAAA;AAEJ,IAAA;AAOF,EAAA;AACO,EAAA;AACR;AAQgB;AAMT,EAAA;AACI,EAAA;AACJ,EAAA;AACA,EAAA;AACA,IAAA;AACD,IAAA;AACE,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACN,EAAA;AACO,IAAA;AACE,IAAA;AACR,IAAA;AACM,MAAA;AACD,MAAA;AACA,MAAA;AACH,QAAA;AACD,MAAA;AACC,QAAA;AACD,MAAA;AACM,MAAA;AACN,MAAA;AACK,MAAA;AACN,IAAA;AACD,EAAA;AACS,EAAA;AACV;AAKM;AAwBU;AAOT,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACJ,EAAA;AACA,EAAA;AACC,IAAA;AACA,IAAA;AACD,IAAA;AACD,IAAA;AACE,IAAA;AACF,IAAA;AACA,IAAA;AACE,IAAA;AACN,IAAA;AACO,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACL,QAAA;AACI,QAAA;AACL,MAAA;AACI,MAAA;AACC,QAAA;AACH,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACA,QAAA;AACD,MAAA;AACC,QAAA;AACI,QAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACI,IAAA;AACG,IAAA;AACP,EAAA;AACQ,EAAA;AACV;AAqBgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACE,MAAA;AACP,IAAA;AACM,IAAA;AACF,IAAA;AACG,IAAA;AACR,EAAA;AACA,EAAA;AACQ,IAAA;AACA,IAAA;AACF,IAAA;AACN,EAAA;AACS,EAAA;AACV;AnBs6IY;AACA;AoBrnKI;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACG,EAAA;AACF,IAAA;AACC,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAMa;AACF,EAAA;AACA,EAAA;AACV,EAAA;AACU,EAAA;AACD,EAAA;AACA,EAAA;AACD,EAAA;AACE,EAAA;AACV,EAAA;AACA,EAAA;AACS,EAAA;AACC,EAAA;AACX;AAiCc;AAEL;AACJ,EAAA;AACG,IAAA;AACA,IAAA;AACC,IAAA;AACR,EAAA;AACI,EAAA;AACI,EAAA;AACF,IAAA;AACJ,MAAA;AACI,IAAA;AACA,IAAA;AACJ,MAAA;AACD,IAAA;AACC,MAAA;AACF,EAAA;AACD;AAEM;AAGG;AACC,EAAA;AACJ,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACI,EAAA;AACL,EAAA;AACE,IAAA;AACA,IAAA;AACL,MAAA;AACA,MAAA;AACI,MAAA;AACJ,IAAA;AACD,EAAA;AACK,EAAA;AACG,EAAA;AACF,EAAA;AACR;AAES;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACD,EAAA;AAGT;AAOgB;AACT,EAAA;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACM,IAAA;AACC,IAAA;AACF,IAAA;AACL,EAAA;AACM,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACN,EAAA;AACOkB,IAAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AAEG,MAAA;AACL,QAAA;AACA,QAAA;AACD,MAAA;AACI,MAAA;AACJ,MAAA;AACD,IAAA;AACI,IAAA;AACG,MAAA;AACF,MAAA;AACH,QAAA;AACI,QAAA;AACL,MAAA;AACK,QAAA;AACH,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACF,MAAA;AACA,MAAA;AACD,IAAA;AACM,IAAA;AACA,IAAA;AACF,IAAA;AACC,MAAA;AACE,IAAA;AACF,MAAA;AACL,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACA,EAAA;AACC,EAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACD;AAcM;AAMU;AAKT,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAA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AAA;AACG,QAAA;AACL,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAEA,IAAA;AACM,MAAA;AACL,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAAA;AAIA,IAAA;AACC,MAAA;AACD,IAAA;AACA,IAAA;AACC,MAAA;AACD,IAAA;AACA,IAAA;AACO,MAAA;AACN,MAAA;AACD,IAAA;AACA,IAAA;AACO,MAAA;AACN,MAAA;AACD,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AtBkxKY;AACA;AuB7xLC;AAEA;AAEA;AAEA;AAEA;AAEA;AAGP;AA4FA;AACA;AAGG;AACE,EAAA;AACN,EAAA;AACI,IAAA;AACR,EAAA;AACU,EAAA;AACA,EAAA;AACH,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACX;AAESC;AACF,EAAA;AACF,EAAA;AAAiB,IAAA;AAAgF,EAAA;AAC9F,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACT,EAAA;AACC,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACL,EAAA;AACH,EAAA;AACR;AAGgB;AACL,EAAA;AACH,EAAA;AACR;AAGgB;AACRA,EAAAA;AACR;AAGgB;AACL,EAAA;AACV,EAAA;AACO,EAAA;AACR;AAES;AACC,EAAA;AACV;AAES;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACJ,EAAA;AACE,EAAA;AACR;AAGS;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AACC,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACR;AAKM;AACL,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACK;AACA;AAEA;AACL,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA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AACR;AAGgB;AACT,EAAA;AACI,EAAA;AACH,EAAA;AACR;AAES;AACC,EAAA;AACV;AAES;AACF,EAAA;AACN,EAAA;AACC,IAAA;AACK,MAAA;AACC,MAAA;AACA,MAAA;AACN,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGS;AACJ,EAAA;AACJ,EAAA;AACS,IAAA;AACF,IAAA;AACD,IAAA;AACJ,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACF,EAAA;AACJ,EAAA;AACO,IAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAEM;AAGG;AACF,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAES;AACD,EAAA;AACR;AAES;AACF,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACN,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACC,EAAA;AACA,IAAA;AACN,IAAA;AACO,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGS;AACH,EAAA;AACC,EAAA;AACI,EAAA;AACJ,EAAA;AACG,EAAA;AACF,EAAA;AACR;AAGgB;AACL,EAAA;AACH,EAAA;AACR;AAMgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACL,EAAA;AACJ,EAAA;AACK,EAAA;AACJ,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACT,EAAA;AACN,EAAA;AACO,IAAA;AACC,IAAA;AACR,EAAA;AACQ,EAAA;AACT;AAGgB;AACN,EAAA;AACV;AAGgB;AACL,EAAA;AACJ,EAAA;AACC,EAAA;AACR;AAoBgB;AACL,EAAA;AACJ,EAAA;AACN,EAAA;AACO,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACN,EAAA;AACM,IAAA;AACC,IAAA;AACN,IAAA;AACKA,MAAAA;AACE,MAAA;AACF,MAAA;AACJ,MAAA;AACA,IAAA;AACF,EAAA;AACM,EAAA;AACC,IAAA;AACA,IAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACC,EAAA;AACO,EAAA;AACV;AA6BgB;AACL,EAAA;AACJ,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACF,IAAA;AACL,EAAA;AACO,EAAA;AACF,IAAA;AACI,IAAA;AACA,IAAA;AACR,IAAA;AACI,IAAA;AACI,IAAA;AACJ,IAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACM,MAAA;AACN,MAAA;AACA,MAAA;AACI,MAAA;AACH,IAAA;AACF,IAAA;AACD,EAAA;AACD;AAYS;AACD,EAAA;AACN,IAAA;AACA,IAAA;AACQ,IAAA;AACA,IAAA;AACT,EAAA;AACD;AAEM;AACC,EAAA;AACN,EAAA;AACO,EAAA;AACR;AAES;AACR,EAAA;AACK,IAAA;AACC,IAAA;AACN,EAAA;AACD;AAMS;AACF,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACC,IAAA;AACA,IAAA;AAEN,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AAC2B,EAAA;AACxB,EAAA;AACR,IAAA;AACO,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACI,EAAA;AACJ,EAAA;AACC,EAAA;AACR;AAGS;AACFoB,EAAAA;AACAlB,EAAAA;AACIA,EAAAA;AACAA,EAAAA;AACLkB,EAAAA;AACA,IAAA;AACE,IAAA;AACC,IAAA;AACA,IAAA;AACR,EAAA;AACM,EAAA;AACC,EAAA;AACR;AAES;AACJ,EAAA;AACA,EAAA;AACC,IAAA;AACE,IAAA;AACC,IAAA;AACR,EAAA;AACU,EAAA;AACH,EAAA;AACR;AAcS;AACF,EAAA;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACD,EAAA;AAGC,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACA,IAAA;AACF,IAAA;AACH,MAAA;AACM,MAAA;AACA,IAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACOpB,IAAAA;AACA,IAAA;AACA,IAAA;AACD,IAAA;AACL,IAAA;AACD,EAAA;AACI,EAAA;AACE,EAAA;AACCqB,IAAAA;AACA,IAAA;AACC,MAAA;AACN,MAAA;AACC,QAAA;AACA,QAAA;AACI,QAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACI,QAAA;AACJ,QAAA;AACD,MAAA;AACD,IAAA;AACK,IAAA;AACEA,IAAAA;AACR,EAAA;AAEM,EAAA;AACA,EAAA;AACC,IAAA;AACF,IAAA;AACJ,IAAA;AACM,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AACAC,EAAAA;AAGA,EAAA;AACC,IAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACA,IAAA;AACL,IAAA;AACItB,IAAAA;AACG,IAAA;AACF,MAAA;AACE,MAAA;AACF,MAAA;AAEH,QAAA;AACA,QAAA;AACD,MAAA;AACI,MAAA;AACA,MAAA;AACA,MAAA;AACL,IAAA;AACO,IAAA;AACR,EAAA;AAGM,EAAA;AACC,IAAA;AACA,IAAA;AACF,IAAA;AACA,IAAA;AACE,IAAA;AACC,MAAA;AACN,MAAA;AACD,IAAA;AACME,IAAAA;AACA,IAAA;AACF,IAAA;AACE,MAAA;AACA,MAAA;AACN,IAAA;AACI,IAAA;AACE,MAAA;AACA,MAAA;AACN,IAAA;AACO,IAAA;AACR,EAAA;AAOM,EAAA;AAWC,IAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACC,IAAA;AACC,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACL,IAAA;AACIF,IAAAA;AACE,IAAA;AACN,IAAA;AACA,IAAA;AACI,IAAA;AACA,IAAA;AACAuB,IAAAA;AAEG,MAAA;AACN,MAAA;AACD,IAAA;AAEIA,IAAAA;AACG,IAAA;AACR,EAAA;AAEM,EAAA;AACC,IAAA;AACC,IAAA;AACAtB,MAAAA;AACDA,MAAAA;AACC,MAAA;AACP,IAAA;AACI,IAAA;AACJ,IAAA;AACO,MAAA;AACD,MAAA;AACL,MAAA;AACC,QAAA;AACI,QAAA;AACA,QAAA;AACL,MAAA;AACD,IAAA;AACO,IAAA;AACR,EAAA;AAEM,EAAA;AACC,IAAA;AACN,IAAA;AACO,MAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACM,MAAA;AACF,MAAA;AACA,MAAA;AACA,MAAA;AACH,QAAA;AACI,QAAA;AACL,MAAA;AACI,MAAA;AACH,QAAA;AACA,QAAA;AACI,QAAA;AACL,MAAA;AACI,MAAA;AACE,MAAA;AACA,MAAA;AACFD,MAAAA;AACE,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACI,QAAA;AACH,UAAA;AACA,UAAA;AAEC,YAAA;AACAA,YAAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACD,UAAA;AACD,QAAA;AACI,QAAA;AACJ,QAAA;AACA,QAAA;AACD,MAAA;AACI,MAAA;AACJ,MAAA;AACD,IAAA;AACD,EAAA;AAEM,EAAA;AACI,EAAA;AACV,EAAA;AAGM,EAAA;AACA,EAAA;AACN,EAAA;AACOA,IAAAA;AACA,IAAA;AACL,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACM,EAAA;AACF,EAAA;AAGE,EAAA;AACA,EAAA;AACD,EAAA;AACE,IAAA;AACA,IAAA;AACE,IAAA;AACT,EAAA;AAES,EAAA;AACF,IAAA;AACF,IAAA;AACL,EAAA;AAEQ,EAAA;AACD,IAAA;AACC,IAAA;AACP,EAAA;AACQ,EAAA;AACV;AAMS;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACC,IAAA;AACC,IAAA;AACR,EAAA;AACU,EAAA;AACJ,EAAA;AACN,EAAA;AACI,EAAA;AACJ,EAAA;AACO,IAAA;AACC,IAAA;AAAsB,IAAA;AAE9B,EAAA;AACS,EAAA;AACF,IAAA;AACA,IAAA;AACC,MAAA;AACN,MAAA;AACA,IAAA;AACG,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAMS;AACJ,EAAA;AACA,EAAA;AACH,IAAA;AACM,IAAA;AACN,IAAA;AACO,MAAA;AACC,MAAA;AACR,IAAA;AACD,EAAA;AACU,EAAA;AACA,EAAA;AACJ,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACC,IAAA;AACF,IAAA;AACC,IAAA;AACG,IAAA;AACT,EAAA;AACM,EAAA;AACA,EAAA;AACF,EAAA;AACK,EAAA;AACF,IAAA;AACN,IAAA;AACO,MAAA;AACN,MAAA;AACK,QAAA;AACJ,QAAA;AACI,QAAA;AACH,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACD,EAAA;AAEM,EAAA;AACN,EAAA;AACO,IAAA;AACE,IAAA;AACT,EAAA;AACA,EAAA;AACO,EAAA;AACR;AAES;AACE,EAAA;AACD,EAAA;AACD,EAAA;AACD,EAAA;AACR;AAKgB;AACT,EAAA;AACC,EAAA;AACR;AAMgB;AACT,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAGS;AACE,EAAA;AACJ,EAAA;AACK,EAAA;AACL,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,IAAA;AACC,MAAA;AACN,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACI,QAAA;AACH,UAAA;AACD,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACKwB,IAAAA;AACN,EAAA;AACU,EAAA;AACJ,EAAA;AACA,EAAA;AACN,EAAA;AACOxB,IAAAA;AACF,IAAA;AACA,IAAA;AACJ,IAAA;AACK,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACM,MAAA;AACP,IAAA;AACA,IAAA;AACM,IAAA;AACF,IAAA;AACG,IAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACI,MAAA;AACJ,MAAA;AACM,IAAA;AACH,IAAA;AACL,EAAA;AACM,EAAA;AACF,EAAA;AACM,EAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACN,IAAA;AACA,IAAA;AACD,EAAA;AACQ,EAAA;AACD,EAAA;AACR;AAGS;AACF,EAAA;AACG,EAAA;AACF,IAAA;AACE,IAAA;AACR,EAAA;AACF;AAgBgB;AAKX,EAAA;AACE,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AAGA,EAAA;AACA,EAAA;AACN,EAAA;AACK,IAAA;AACE,IAAA;AACD,IAAA;AACG,IAAA;AACT,EAAA;AACI,EAAA;AACG,IAAA;AACN,IAAA;AACA,IAAA;AACO,MAAA;AACC,MAAA;AACR,IAAA;AACD,EAAA;AAGQ,EAAA;AACF,EAAA;AACI,EAAA;AACH,IAAA;AACA,IAAA;AACN,IAAA;AACMA,MAAAA;AACC,MAAA;AACD,MAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,MAAA;AACC,MAAA;AACC,MAAA;AACP,IAAA;AACI,IAAA;AACH,MAAA;AACI,MAAA;AACL,IAAA;AACM,IAAA;AACA,IAAA;AACN,IAAA;AACO,MAAA;AACF,MAAA;AACC,QAAA;AACJ,QAAA;AACD,MAAA;AACI,MAAA;AACE,MAAA;AACD,MAAA;AACN,IAAA;AACI,IAAA;AACJ,IAAA;AACQ,IAAA;AACT,EAAA;AAGM,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACC,IAAA;AACD,IAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACC,MAAA;AACA,MAAA;AACL,IAAA;AACI,IAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACA,MAAA;AACI,MAAA;AACL,IAAA;AACM,IAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACA,MAAA;AACM,MAAA;AACF,MAAA;AACL,IAAA;AACI,IAAA;AACG,MAAA;AACC,MAAA;AACR,IAAA;AACD,EAAA;AACI,EAAA;AACK,IAAA;AACA,IAAA;AACT,EAAA;AAGM,EAAA;AACN,EAAA;AACO,IAAA;AACF,IAAA;AACE,IAAA;AACF,IAAA;AACH,MAAA;AACD,IAAA;AACQ,IAAA;AACA,IAAA;AACD,MAAA;AACN,MAAA;AACD,IAAA;AACM,IAAA;AAMA,IAAA;AACD,IAAA;AACC,IAAA;AACP,EAAA;AACA,EAAA;AACK,IAAA;AACE,IAAA;AACF,IAAA;AACL,EAAA;AAEA,EAAA;AACO,IAAA;AACC,MAAA;AACN,MAAA;AACA,MAAA;AACD,IAAA;AACM,IAAA;AACA,IAAA;AACN,IAAA;AACK,MAAA;AACE,MAAA;AACF,MAAA;AACL,IAAA;AACD,EAAA;AAEQ,EAAA;AACD,EAAA;AACR;AAKS;AACC,EAAA;AACV;AAES;AAMC,EAAA;AACV;AAES;AACC,EAAA;AACA,EAAA;AACF,EAAA;AACR;AAES;AACD,EAAA;AACR;AAGS;AACE,EAAA;AACJ,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACJ,EAAA;AACS,IAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACR,EAAA;AACQ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD,IAAA;AACE,IAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACO,IAAA;AACD,IAAA;AACF,IAAA;AACL,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD,IAAA;AACA,IAAA;AACJ,EAAA;AACM,EAAA;AACR;AAOgB;AAKL,EAAA;AACJ,EAAA;AACA,EAAA;AACI,EAAA;AACA,EAAA;AACF,IAAA;AACR,EAAA;AACU,EAAA;AACF,IAAA;AACR,EAAA;AACQ,EAAA;AACF,EAAA;AACI,EAAA;AACH,IAAA;AACA,IAAA;AACF,IAAA;AACGyB,MAAAA;AACN,MAAA;AACD,IAAA;AACD,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAMgB;AAKL,EAAA;AACJ,EAAA;AACF,EAAA;AACC,EAAA;AACD,EAAA;AACA,EAAA;AACI,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACH,IAAA;AACM,IAAA;AACE,IAAA;AACR,IAAA;AACA,EAAA;AAEK,EAAA;AACN,EAAA;AACQ,EAAA;AACD,IAAA;AACE,IAAA;AACJ,IAAA;AACA,IAAA;AACE,IAAA;AACC,IAAA;AACP,EAAA;AACM,EAAA;AACR;AAMgB;AAKL,EAAA;AACJ,EAAA;AACK,EAAA;AACL,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACE,EAAA;AACA,EAAA;AACI,EAAA;AACN,EAAA;AACC,EAAA;AACA,IAAA;AACC,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACD,EAAA;AACM,EAAA;AACL,IAAA;AACM,IAAA;AACN,IAAA;AACQ,IAAA;AACJ,IAAA;AACL,EAAA;AACM,EAAA;AACF,EAAA;AACA,EAAA;AACG,IAAA;AACD,IAAA;AACG,IAAA;AACT,EAAA;AACS,EAAA;AACV;AAOgB;AAKX,EAAA;AACE,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACD,IAAA;AACH,MAAA;AACD,IAAA;AACM,IAAA;AACA,IAAA;AACN,IAAA;AACK,MAAA;AACA,MAAA;AACC,MAAA;AACN,IAAA;AACM,IAAA;AACA,IAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACR,IAAA;AACA,IAAA;AACQ,IAAA;AACT,EAAA;AACI,EAAA;AACG,EAAA;AACR;AAOgB;AAML,EAAA;AACJ,EAAA;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACC,IAAA;AACA,IAAA;AACC,IAAA;AACR,EAAA;AACI,EAAA;AACC,IAAA;AACA,IAAA;AACI,IAAA;AACT,EAAA;AACC,IAAA;AACOzB,MAAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AACS,IAAA;AACH,EAAA;AACD,EAAA;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACG,EAAA;AACV;AAOgB;AAKL,EAAA;AACJ,EAAA;AACK,EAAA;AACL,EAAA;AACD,EAAA;AACC,EAAA;AACF,EAAA;AACJ,EAAA;AACOA,IAAAA;AACA,IAAA;AACF,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACJ,MAAA;AACF,IAAA;AACI,IAAA;AACJ,IAAA;AACQ,IAAA;AACA,IAAA;AACT,EAAA;AACI,EAAA;AACE,EAAA;AACC,EAAA;AACR;AAGgB;AAML,EAAA;AACJ,EAAA;AACK,EAAA;AACL,EAAA;AACA,EAAA;AACN,EAAA;AACOA,IAAAA;AACA,IAAA;AACDA,IAAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACN,IAAA;AACQ,IAAA;AACJ,IAAA;AACH,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACS,EAAA;AACH,EAAA;AACC,EAAA;AACR;AAOgB;AAML,EAAA;AACJ,EAAA;AACA,EAAA;AACG,EAAA;AACT,EAAA;AACOA,IAAAA;AACA,IAAA;AACDA,IAAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACN,IAAA;AACO,MAAA;AACF,MAAA;AACH,QAAA;AACI,QAAA;AACJ,QAAA;AACM,MAAA;AACR,IAAA;AACI,IAAA;AACA,IAAA;AACE,IAAA;AACA,IAAA;AACN,IAAA;AACO,MAAA;AACD,MAAA;AACJ,QAAA;AACA,QAAA;AACD,MAAA;AACI,MAAA;AACH,QAAA;AACI,QAAA;AACJ,QAAA;AACD,MAAA;AACK,QAAA;AACJ,QAAA;AACD,MAAA;AACC,QAAA;AACI,QAAA;AACA,QAAA;AACH,UAAA;AACD,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACM,IAAA;AACE,IAAA;AACT,EAAA;AACM,EAAA;AACC,EAAA;AACR;AAMgB;AAOL,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD,EAAA;AACD,EAAA;AACI,IAAA;AACR,EAAA;AACI,EAAA;AACA,EAAA;AACE,IAAA;AACE,IAAA;AACD,EAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACJ,IAAA;AACC,IAAA;AACN,EAAA;AACU,EAAA;AACJ,EAAA;AACA,EAAA;AACE,EAAA;AACF,EAAA;AACF,EAAA;AACH,IAAA;AACC,MAAA;AACC,QAAA;AACA,QAAA;AACI,QAAA;AACJ,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AAAuC,UAAA;AAExC,QAAA;AACI,QAAA;AACJ,QAAA;AACI,QAAA;AACJ,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACO,MAAA;AACD,MAAA;AACC,MAAA;AACA,MAAA;AACN,MAAA;AACI,MAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACD,EAAA;AACM,EAAA;AACC,EAAA;AACR;AAOgB;AAOL,EAAA;AACJ,EAAA;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACF,EAAA;AACE,EAAA;AACF,EAAA;AACC,EAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AAIG,IAAA;AACJ,IAAA;AACN,EAAA;AACM,EAAA;AACF,EAAA;AACC,EAAA;AACC,EAAA;AACC,EAAA;AACR;AAOgB;AAKL,EAAA;AACJ,EAAA;AACK,EAAA;AACD,EAAA;AACN,EAAA;AACE,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACV,EAAA;AACK,IAAA;AACE,IAAA;AACF,IAAA;AACC,MAAA;AACE,MAAA;AACP,IAAA;AACD,EAAA;AACM,EAAA;AACA,EAAA;AACN,EAAA;AACKA,IAAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACK,QAAA;AAAgE,QAAA;AACwC,QAAA;AAE7G,MAAA;AACM,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACK,MAAA;AACD,MAAA;AACL,IAAA;AACK,MAAA;AACCA,MAAAA;AACN,IAAA;AACA,IAAA;AACD,EAAA;AACI,EAAA;AACJ,EAAA;AACO,EAAA;AACR;AA4BM;AAEA;AAEG;AACF,EAAA;AACE,EAAA;AACJ,EAAA;AACG,EAAA;AACR;AAGS;AACF,EAAA;AACN,EAAA;AACK,IAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACN,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACF,EAAA;AACG,EAAA;AACR;AAMS;AACF,EAAA;AACF,EAAA;AACE,EAAA;AACL,IAAA;AACO,IAAA;AACR,EAAA;AACA,EAAA;AACO,IAAA;AACA0B,IAAAA;AACA,IAAA;AACD,IAAA;AACN,EAAA;AACO,EAAA;AACR;AAQS;AACF,EAAA;AACD,EAAA;AACD,EAAA;AACE,EAAA;AACG,EAAA;AACV;AAGS;AACF,EAAA;AACN,EAAA;AACM,EAAA;AACI,EAAA;AACJ,EAAA;AACF,EAAA;AACE,EAAA;AACA,EAAA;AACK,EAAA;AACX,EAAA;AACO,IAAA;AACN,IAAA;AACK,IAAA;AACN,EAAA;AACO,EAAA;AACR;AAGS;AACR,EAAA;AACO,IAAA;AACN,IAAA;AACK,MAAA;AACL,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACF,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACF,IAAA;AACD,IAAA;AACN,EAAA;AACD;AAMgB;AACL,EAAA;AACH,EAAA;AACR;AAES;AACJ,EAAA;AACG,EAAA;AACR;AAQgB;AAKL,EAAA;AACJ,EAAA;AACA,EAAA;AACG,EAAA;AACH,EAAA;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACF,EAAA;AACE,EAAA;AACF,EAAA;AACE,EAAA;AACI,EAAA;AACL,EAAA;AACC,EAAA;AACG,EAAA;AAGH,EAAA;AACE,EAAA;AACJ,EAAA;AACC,EAAA;AACE,IAAA;AACN,IAAA;AACA,IAAA;AACO,MAAA;AACD,MAAA;AACL,MAAA;AACD,IAAA;AACD,EAAA;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2B13QLM;AAmCM;AACA;AAGG;AACR,EAAA;AACA,EAAA;AACF,EAAA;AACA,EAAA;AACE,EAAA;AACK,EAAA;AACH,EAAA;AACD,IAAA;AACA,IAAA;AACH,MAAA;AACA,MAAA;AACG,IAAA;AACH,MAAA;AACA,MAAA;AACA,MAAA;AACG,IAAA;AACG,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACK,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACH,MAAA;AACA,MAAA;AACA,MAAA;AACG,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACG,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACK,IAAA;AACA,IAAA;AACH,MAAA;AACA,MAAA;AACA,MAAA;AACG,IAAA;AACA,IAAA;AACH,MAAA;AACA,MAAA;AACA,MAAA;AACG,IAAA;AACH,MAAA;AACA,MAAA;AACA,MAAA;AACG,IAAA;AACA,IAAA;AACA,IAAA;AACH,MAAA;AACA,MAAA;AACG,IAAA;AAEG,MAAA;AACA,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACA,IAAA;AACE,MAAA;AACJ,EAAA;AACS,EAAA;AACX;AAGM;AAGU;AACRC,EAAAA;AACIA,EAAAA;AACAA,EAAAA;AACD,EAAA;AACX;AAES;AACD,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACT;AASS;AACG,EAAA;AACA,EAAA;AACA,EAAA;AACH,EAAA;AACT;AAOgB;AACR,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEA,EAAA;AACN,EAAA;AACA,EAAA;AACM,EAAA;AACA,EAAA;AAEA,EAAA;AACA,EAAA;AAEA,EAAA;AACE,IAAA;AAEN,IAAA;AACQ,MAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACF,MAAA;AACE,QAAA;AACF,MAAA;AACF,IAAA;AACK,IAAA;AAEC,IAAA;AACJ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AAA;AACA,MAAA;AACD,MAAA;AACL,MAAA;AACI,MAAA;AACJ,IAAA;AACK,IAAA;AACC,MAAA;AACD,MAAA;AACC,MAAA;AACN,MAAA;AACK,MAAA;AACA,MAAA;AACC,MAAA;AACN,MAAA;AACA,MAAA;AACMA,MAAAA;AACA,MAAA;AACN,IAAA;AACF,EAAA;AACO,EAAA;AACR;ArCy5SY;AACA;AsCjgTF;AAKD;AACG,EAAA;AACD,EAAA;AACA,EAAA;AACF,EAAA;AACT;AAEM;AACAC;AAEG;AACF,EAAA;AACA,EAAA;AACI,EAAA;AACA,EAAA;AACD,EAAA;AACV;AAMS;AACF,EAAA;AACN,EAAA;AACO,IAAA;AACF,IAAA;AACH,MAAA;AACM,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACI,EAAA;AACJ,IAAA;AACJ,MAAA;AACG,QAAA;AACA,QAAA;AACH,MAAA;AACI,IAAA;AACJ,MAAA;AACG,QAAA;AACA,QAAA;AACA,QAAA;AACF,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACD,IAAA;AACO,MAAA;AACA,MAAA;AACL,QAAA;AACK,MAAA;AACL,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACF,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAGS;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AAEA,EAAA;AACE,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AACE,IAAA;AACA,IAAA;AACT,EAAA;AACD;AAGS;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACD,MAAA;AACA,MAAA;AACC,MAAA;AACP,IAAA;AACA,IAAA;AACQ,IAAA;AACA,IAAA;AACF,EAAA;AAEA,IAAA;AACA,IAAA;AACAC,IAAAA;AACC,MAAA;AACN,MAAA;AACD,IAAA;AACA,IAAA;AACOA,IAAAA;AACCA,IAAAA;AACR,IAAA;AACD,EAAA;AACO,EAAA;AACE,IAAA;AACA,IAAA;AACR,IAAA;AACO,MAAA;AACD,MAAA;AACC,MAAA;AACN,MAAA;AACG,MAAA;AACA,MAAA;AACH3B,MAAAA;AACA,MAAA;AACA,MAAA;AACMyB,MAAAA;AACA,MAAA;AACF,MAAA;AACL,IAAA;AACD,EAAA;AACD;AASa;AACN,EAAA;AACA,EAAA;AACN,EAAA;AACS,IAAA;AACF,IAAA;AACP,EAAA;AACA,EAAA;AACO,IAAA;AACD,IAAA;AACC,IAAA;AACC,MAAA;AACD,MAAA;AACC,MAAA;AACN,MAAA;AACG,MAAA;AACG,MAAA;AACN,MAAA;AACA,MAAA;AACK,MAAA;AACL,IAAA;AACF,EAAA;AACM,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACG,IAAA;AACF,IAAA;AACA,IAAA;AACH,MAAA;AACF,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACG,QAAA;AACA,QAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACA,EAAA;AACO,IAAA;AACE,IAAA;AACF,IAAA;AACA,IAAA;AACC,IAAA;AACR,EAAA;AACO,EAAA;AACR;AtCw+SY;AACA;AuCjsTF;AAIJC;AAGG;AACJ1B,EAAAA;AACK,EAAA;AACFA,EAAAA;AAIR;AAgBS;AACC,EAAA;AACA,EAAA;AACH,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACN,IAAA;AACI,MAAA;AACA,MAAA;AACJ,IAAA;AACM,IAAA;AACE,IAAA;AACT,EAAA;AAEM,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAES;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACA,EAAA;AACJ,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACG,IAAA;AACA,IAAA;AACP,EAAA;AACMgB,EAAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACF,EAAA;AACD,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACF,IAAA;AACF,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAEa;AAGJ;AACA,EAAA;AACF,EAAA;AACA,EAAA;AACI,EAAA;AACJ,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACE,MAAA;AACA,MAAA;AACD,QAAA;AACH,QAAA;AACG,QAAA;AACH,QAAA;AACG,QAAA;AACA,QAAA;AACL,MAAA;AACA,MAAA;AACD,IAAA;AACK,IAAA;AACE,MAAA;AACAY,MAAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,QAAA;AACA,QAAA;AACAA,QAAAA;AACA,QAAA;AACA,QAAA;AACAA,QAAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACJ,MAAA;AACD,MAAA;AACD,IAAA;AACK,IAAA;AACE,MAAA;AACA,MAAA;AACD,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACJ,MAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AAEC,MAAA;AACG,QAAA;AACA,QAAA;AACH,MAAA;AACF,EAAA;AACD;AAGS;AAEF,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAGS;AACF,EAAA;AACA,EAAA;AACE,EAAA;AACT;AAUa;AACN,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACD,IAAA;AACC,IAAA;AACP,EAAA;AACA,EAAA;AACO,IAAA;AACE,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACD5B,MAAAA;AACDA,MAAAA;AACC,MAAA;AACL,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACG,QAAA;AACA,QAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACD,IAAA;AACK,IAAA;AACF,IAAA;AACG,MAAA;AACH,QAAA;AACA,QAAA;AACH,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AvCwpTY;AACA;AwC73TN;AACK,EAAA;AACA,EAAA;AACV,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AACM;AAEA;AACA;AACA;AACA6B;AACA;AACA;AACA;AACA;AAEG;AACD,EAAA;AACR;AAEM;AAGG;AACH,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAYa;AACN,EAAA;AACA,EAAA;AACC,IAAA;AACA,IAAA;AACC,IAAA;AACR,EAAA;AAGM,EAAA;AACAjC,EAAAA;AACG,IAAA;AACF,IAAA;AACE,IAAA;AACD,IAAA;AACD,MAAA;AACL,MAAA;AACI,MAAA;AACL,IAAA;AACO,IAAA;AACR,EAAA;AACA,EAAA;AACO,IAAA;AACE,IAAA;AACFI,IAAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACFA,IAAAA;AACG,MAAA;AACR,EAAA;AAGM,EAAA;AACA,EAAA;AACA,EAAA;AACC,IAAA;AACA,IAAA;AACD,IAAA;AACC,IAAA;AACE,IAAA;AACF,IAAA;AACAwB,IAAAA;AACA,IAAA;AACC,MAAA;AACD,MAAA;AACC,MAAA;AACN,MAAA;AACG,MAAA;AACG,MAAA;AACN,MAAA;AACA,MAAA;AACA,IAAA;AACK,IAAA;AAEF,IAAA;AACA,IAAA;AAEE,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACH,MAAA;AACG,IAAA;AACH,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACG,QAAA;AACA,QAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACF,EAAA;AAGD,EAAA;AACO,IAAA;AACE,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACD,MAAA;AACA,MAAA;AACH,QAAA;AACA,QAAA;AACH,MAAA;AACD,IAAA;AACO,MAAA;AACA,MAAA;AACA,MAAA;AACL,QAAA;AACA,QAAA;AACA,QAAA;AACG,QAAA;AACA,QAAA;AACH,QAAA;AACA,MAAA;AACF,IAAA;AACO,MAAA;AACA,MAAA;AACA,MAAA;AACL,QAAA;AACA,QAAA;AACA,QAAA;AACG,QAAA;AACA,QAAA;AACH,QAAA;AACA,MAAA;AACK,IAAA;AAGH,IAAA;AACJ,IAAA;AACOvC,MAAAA;AACA,MAAA;AACN,MAAA;AACK,QAAA;AACJ,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACD,MAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AAEM,EAAA;AACA,EAAA;AACK,EAAA;AACJ,EAAA;AACR;AxCo2TY;AACA;AyCtjUF;AAEJ;AACA;AAMMF;AAOH;AACF,EAAA;AACA,EAAA;AACI,EAAA;AACF,EAAA;AACE,EAAA;AACF,EAAA;AACJ,EAAA;AACEiC,EAAAA;AACC,EAAA;AACF,IAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACF,MAAA;AACA,QAAA;AACA,QAAA;AACJ,QAAA;AACA,QAAA;AACI,QAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACI,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACJ,QAAA;AACA,QAAA;AACI,MAAA;AACA,QAAA;AACA,QAAA;AACJ,QAAA;AACA,QAAA;AACI,MAAA;AACJ,QAAA;AACI,QAAA;AACJ,QAAA;AACA,QAAA;AACI,MAAA;AACJ,QAAA;AACI,QAAA;AACA,QAAA;AACA,QAAA;AACJ,QAAA;AACA,QAAA;AACD,MAAA;AACK,MAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACI,QAAA;AACA,QAAA;AACA,QAAA;AACJ,QAAA;AACA,QAAA;AACD,MAAA;AACK,MAAA;AACJ,QAAA;AACI,QAAA;AACA,QAAA;AACA,QAAA;AACJ,QAAA;AACA,QAAA;AACD,MAAA;AACK,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACJ,QAAA;AACA,QAAA;AACD,MAAA;AACC,QAAA;AACF,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGS;AACE,EAAA;AACH,EAAA;AACR;AAGS;AACD,EAAA;AACR;AAEa;AAKA;AACC;AAOR;AACK,EAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACAQ,IAAAA;AACAA,IAAAA;AACN,EAAA;AACQ,EAAA;AACF,IAAA;AACAA,IAAAA;AACAA,IAAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACT,EAAA;AACM,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACN,EAAA;AACU,EAAA;AACJ,IAAA;AACAA,IAAAA;AACAA,IAAAA;AACA,IAAA;AACA,IAAA;AACAA,IAAAA;AACAA,IAAAA;AACN,EAAA;AACO,EAAA;AACN,IAAA;AACI,MAAA;AACH,MAAA;AACD,IAAA;AACD,EAAA;AACQ,EAAA;AACT;AAOS;AACEvC,EAAAA;AACL,EAAA;AACG,IAAA;AACN,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACG,QAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACK,EAAA;AACA,EAAA;AACF,EAAA;AACG,IAAA;AAAS,MAAA;AAEb,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACG,QAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACF,IAAA;AACO,IAAA;AACR,EAAA;AAEM,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACH,EAAA;AACA,EAAA;AACA,EAAA;AAAe,IAAA;AAErB,EAAA;AACO,EAAA;AACR;AAMM;AACA;AAGG6C;AACH,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAWa;AACN,EAAA;AACA,EAAA;AACC,IAAA;AACA,IAAA;AACC,IAAA;AACR,EAAA;AAGM,EAAA;AACA,EAAA;AACAlC,EAAAA;AACG,IAAA;AACF,IAAA;AACE,IAAA;AACD,IAAA;AACD,MAAA;AACL,MAAA;AACI,MAAA;AACL,IAAA;AACO,IAAA;AACR,EAAA;AACA,EAAA;AACO,IAAA;AACE,IAAA;AACF,IAAA;AACA,IAAA;AACF,IAAA;AACE,IAAA;AACF,IAAA;AACG,MAAA;AACR,EAAA;AAGM,EAAA;AACA,EAAA;AACA,EAAA;AAEN,EAAA;AACO,IAAA;AACA,IAAA;AACD,IAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACH,MAAA;AACF,EAAA;AAGA,EAAA;AACO,IAAA;AACE,IAAA;AACJ,IAAA;AACG,MAAA;AACA,MAAA;AAEA,MAAA;AACH,QAAA;AACA,QAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACM,IAAA;AACF,IAAA;AACJ,IAAA;AAA6CI,MAAAA;AAE7C,IAAA;AACD,EAAA;AAEM,EAAA;AACA,EAAA;AACK,EAAA;AACJ,EAAA;AACR;AzC4/TY;AACA;A0Cp0UF;AAGG;AAIJ;AACF,EAAA;AACAT,EAAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACA,EAAA;AACM,EAAA;AACA,EAAA;AACN,EAAA;AACG,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACE,EAAA;AACF,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACD,IAAA;AACC,MAAA;AACF,EAAA;AACD;AAGS;AACF,EAAA;AACF,EAAA;AACG,EAAA;AACR;AAGM;AAGG;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACH,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACC,EAAA;AACR;AAUa;AACN,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAGN,EAAA;AACO,IAAA;AACA,IAAA;AACC,MAAA;AACD,MAAA;AACC,MAAA;AACN,MAAA;AACG,MAAA;AACA,MAAA;AACH,MAAA;AACA,MAAA;AACM,MAAA;AACN,MAAA;AACA,IAAA;AACG,IAAA;AACG,MAAA;AACR,EAAA;AAGA,EAAA;AACO,IAAA;AACH,MAAA;AACA,MAAA;AACH,IAAA;AACD,EAAA;AACQ,EAAA;AACD,IAAA;AAGP,EAAA;AACM,IAAA;AACC,IAAA;AACA,IAAA;AACD,IAAA;AACC,IAAA;AACP,EAAA;AAGA,EAAA;AACO,IAAA;AACA,IAAA;AACAwC,IAAAA;AACA,IAAA;AACE,IAAA;AAED,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACL,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACG,QAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACK,IAAA;AACA,MAAA;AACP,IAAA;AACM,IAAA;AACA,IAAA;AACC,MAAA;AACD,MAAA;AACC,MAAA;AACN,MAAA;AACG,MAAA;AACA,MAAA;AACG,MAAA;AACN,MAAA;AACA,MAAA;AACM,MAAA;AACA,MAAA;AACF,MAAA;AACJ,IAAA;AACF,EAAA;AAGU,EAAA;AACH,IAAA;AACA,EAAA;AACR;A1CqyUY;AACA;A2C38UF;AACJ;AAEA;AACA;AAGG;AACJ,EAAA;AACK,EAAA;AACV;AAGS;AACE,EAAA;AACH,EAAA;AACR;AAGS;AACF,EAAA;AACN,EAAA;AACKP,IAAAA;AACJ,IAAA;AACO,MAAA;AACFA,MAAAA;AACC,QAAA;AACJA,QAAAA;AACM,MAAA;AACR,IAAA;AACI,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACE,IAAA;AACR,IAAA;AACO,MAAA;AACD,MAAA;AACC,MAAA;AACH,MAAA;AACG,MAAA;AACP,IAAA;AACD,EAAA;AACD;AASa;AACN,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACAA,EAAAA;AAEA,EAAA;AACC,IAAA;AACA,IAAA;AAEF,IAAA;AACE,IAAA;AACDxB,IAAAA;AAA2C,MAAA;AAEhD,IAAA;AACD,EAAA;AAGA,EAAA;AACM,EAAA;AAGN,EAAA;AACA,EAAA;AACA,EAAA;AACO,IAAA;AACF,IAAA;AACL,EAAA;AAGU,EAAA;AACH,IAAA;AACA,EAAA;AACR;A3Co7UY;AACA;A4CxgVC;AACP,EAAA;AACI,EAAA;AACF,EAAA;AACG,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACD,EAAA;AACL,EAAA;AACJ,EAAA;AACO,EAAA;AACG,EAAA;AACX;A5C0gVY;AACA;A6C/hVN;AAGU;AACR,EAAA;AACR;AAEgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAES;AACA,EAAA;AACF,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AACK,IAAA;AACA,MAAA;AAEE,MAAA;AACF,MAAA;AACA,MAAA;AACJ,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAGgB;AACN,EAAA;AACT,EAAA;AACO,IAAA;AACE,IAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACN,EAAA;AACI,EAAA;AACK,EAAA;AACV;AAYgB;AACT,EAAA;AACD,EAAA;AACCP,EAAAA;AACC,EAAA;AACR;AAGgB;AAMT,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAEaO;AACA;AAGG;AACR,EAAA;AACR;AAGa;AAGG;AACT,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AAEE,IAAA;AACF,MAAA;AACA,QAAA;AACH,UAAA;AACD,QAAA;AACA,QAAA;AACI,MAAA;AACA,QAAA;AACH,UAAA;AACD,QAAA;AACA,QAAA;AACI,MAAA;AACA,QAAA;AACH,UAAA;AACD,QAAA;AACA,QAAA;AACI,MAAA;AACA,QAAA;AACH,UAAA;AACD,QAAA;AACA,QAAA;AACI,MAAA;AACA,QAAA;AACH,UAAA;AACD,QAAA;AACA,QAAA;AACF,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAQa;AACT,EAAA;AACH,EAAA;AACU,EAAA;AACV,EAAA;AACM,EAAA;AACA,EAAA;AACN,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACM,EAAA;AACN,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAOgB;AACP,EAAA;AACJ,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACH,EAAA;AACR;AAGgB;AACP,EAAA;AACJ,EAAA;AACG,EAAA;AACR;A7C++UY;AACA;A8CxqVD;AACG;AACA;AACJ,EAAA;AACH,EAAA;AACC,EAAA;AACR;AACMgC;AAEO;AAOG;AACL,EAAA;AACA,EAAA;AACA,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACE,EAAA;AACF,IAAA;AACA,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACA,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACA,IAAA;AACE,MAAA;AACN,MAAA;AACC,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACK,IAAA;AACA,IAAA;AACE,MAAA;AACA,MAAA;AACL,QAAA;AACA,QAAA;AACD,MAAA;AACI,MAAA;AACJ,MAAA;AACD,IAAA;AACK,IAAA;AACA,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACA,IAAA;AACA,IAAA;AACJ,MAAA;AACI,IAAA;AACA,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACI,IAAA;AACA,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACA,IAAA;AACA,IAAA;AACJ,MAAA;AACI,IAAA;AACA,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACA,IAAA;AACJ,MAAA;AACI,IAAA;AACA,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACA,IAAA;AACJ,MAAA;AACI,IAAA;AACA,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACA,IAAA;AACA,IAAA;AACE,MAAA;AACA,MAAA;AACF,MAAA;AACA,MAAA;AACJ,MAAA;AACD,IAAA;AACK,IAAA;AACE,MAAA;AACA,MAAA;AAEN,MAAA;AACD,IAAA;AACK,IAAA;AACJ,MAAA;AACI,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACJ,MAAA;AACI,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACJ,MAAA;AACI,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACF,MAAA;AACA,MAAA;AACJ,MAAA;AACD,IAAA;AACK,IAAA;AACJ,MAAA;AACI,IAAA;AACA,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACD,IAAA;AACC,MAAA;AACF,EAAA;AACD;AAOgB;AACX,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAQgB;AACT,EAAA;AACL,IAAA;AACA,IAAA;AACO,IAAA;AACP,IAAA;AACD,EAAA;AACI,EAAA;AACG,EAAA;AACR;AAGa;AACH,EAAA;AACD,EAAA;AACE,EAAA;AACA,EAAA;AACX;AAGgB;AACR,EAAA;AAIR;AAGgB;AACT,EAAA;AACC,EAAA;AACR;A9C4oVY;AACA;A+C32VChC;AACF;AAUF;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACA,EAAA;AACI,EAAA;AACD,EAAA;AACV;AAUgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACAP,EAAAA;AACAwC,EAAAA;AACC,IAAA;AACE,IAAA;AACF,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACI,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACA,IAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACR,EAAA;AACI,EAAA;AACE,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACK,EAAA;AACI,EAAA;AACL,EAAA;AACC,IAAA;AACG,IAAA;AACR,EAAA;AAGMrC,EAAAA;AACG,IAAA;AACJ,IAAA;AACG,IAAA;AACN,MAAA;AACM,MAAA;AACP,IAAA;AACO,IAAA;AACR,EAAA;AACA,EAAA;AACO,IAAA;AACE,IAAA;AACF,IAAA;AACF,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AAGA,EAAA;AACO,IAAA;AACA,IAAA;AACD,IAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACH,MAAA;AACD,IAAA;AACM,IAAA;AACCI,MAAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AAGA,EAAA;AACO,IAAA;AACE,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AAQF,IAAA;AACJ,IAAA;AACM,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACA,MAAA;AACF,MAAA;AACJ,MAAA;AACM,IAAA;AACA,MAAA;AACA,MAAA;AACP,IAAA;AACI,IAAA;AACL,EAAA;AACS,EAAA;AACA,EAAA;AACV;AAGgB;AACR,EAAA;AACR;AAGa;AAyBG;AACT,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAEM;AASN;AACO,EAAA;AACA,EAAA;AACD,EAAA;AACCf,EAAAA;AACA,EAAA;AACC,EAAA;AACA,EAAA;AACH,EAAA;AACA,EAAA;AACH,IAAA;AACO,EAAA;AACP,IAAA;AACD,EAAA;AACK,EAAA;AACC,EAAA;AACI,EAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACN,EAAA;AACK,EAAA;AACF,EAAA;AACK,IAAA;AACA,IAAA;AACT,EAAA;AACQ,EAAA;AACF,EAAA;AACG,EAAA;AACA,EAAA;AACV;AAGgB;AACT,EAAA;AAKI,EAAA;AACX;AAQA;AACO,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD,EAAA;AACA,EAAA;AACK,EAAA;AACL,EAAA;AACA,EAAA;AACI,EAAA;AACV;A/C4xVY;AACA;AgDzjWI;AACTQ,EAAAA;AACC,EAAA;AACC,IAAA;AACA,IAAA;AACPA,IAAAA;AACD,EAAA;AACD;AAgBgB;AAOT,EAAA;AACA,EAAA;AACC,IAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACEI,IAAAA;AACCA,IAAAA;AACT,EAAA;AACM,EAAA;AAGE,IAAA;AACE,IAAA;AACR,EAAA;AAEI,EAAA;AACN,EAAA;AACK,IAAA;AACA,IAAA;AACG,IAAA;AACH,IAAA;AACL,EAAA;AACK,EAAA;AACD,EAAA;AACG,IAAA;AACN,IAAA;AACM,MAAA;AACN,IAAA;AACD,EAAA;AACS,EAAA;AACV;AhDmiWY;AACA;AiDhoWC;AA6BG;AACX,EAAA;AACK,EAAA;AACA,IAAA;AACA,IAAA;AACT,EAAA;AACO,EAAA;AACR;AAIa;AACF,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACX;AAMgB;AAMT,EAAA;AACA,EAAA;AAGA,EAAA;AACA,EAAA;AAGA,EAAA;AACA,EAAA;AAGA,EAAA;AACA,EAAA;AAEA,EAAA;AACA,EAAA;AAEC,EAAA;AACE,IAAA;AACA,IAAA;AACR,IAAA;AACD,EAAA;AACD;AAWgB;AAQL,EAAA;AACJ,EAAA;AACA,EAAA;AAGAqC,EAAAA;AAEA,EAAA;AAEA,EAAA;AACA,EAAA;AAEG,EAAA;AACV;AAMgB;AAQT,EAAA;AACA,EAAA;AAGA,EAAA;AACA,EAAA;AACA,EAAA;AAGA,EAAA;AACA,EAAA;AACA,EAAA;AAGA,EAAA;AACA,EAAA;AAEC,EAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACD;AAKgB;AAKT,EAAA;AACA,EAAA;AAEA,EAAA;AAEF,EAAA;AACA,EAAA;AACH,IAAA;AACD,EAAA;AACC,IAAA;AACD,EAAA;AACM,EAAA;AACC,EAAA;AACR;AAMgB;AAOT,EAAA;AACA,EAAA;AAED,EAAA;AACG,IAAA;AACR,EAAA;AAEM,EAAA;AACA,EAAA;AACA,EAAA;AAEF,EAAA;AACI,IAAA;AACR,EAAA;AAEM,EAAA;AAGA,EAAA;AAGA,EAAA;AACC,EAAA;AACN,IAAA;AACA,IAAA;AACD,EAAA;AACD;AAKgB;AACV,EAAA;AACC,EAAA;AACC,EAAA;AACR;AAKgB;AACX,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEE,EAAA;AAEL,EAAA;AAQO,IAAA;AACR,EAAA;AAEM,EAAA;AACF,EAAA;AACI,IAAA;AACR,EAAA;AAEO,EAAA;AACR;AjDw/VY;AACA;AkDtsWC;AAGA;AACZ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAGa;AACT,EAAA;AACH,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAEc;AACN,EAAA;AACC,EAAA;AACT;AAGgB;AACN,EAAA;AACD,EAAA;AACA,EAAA;AACF,EAAA;AACF,EAAA;AACG,EAAA;AACD,IAAA;AACC,IAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACL,IAAA;AACD,EAAA;AACQ,EAAA;AACC,EAAA;AACV;AAUgB;AAGT,EAAA;AACA,EAAA;AACA,EAAA;AAEA,EAAA;AAEA,EAAA;AACC,IAAA;AACA,IAAA;AACD,IAAA;AACE,IAAA;AACR,EAAA;AAEO,EAAA;AACC,IAAA;AACP,IAAA;AACA,IAAA;AACA,IAAA;AACO,MAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AACO,IAAA;AACN,MAAA;AACM,MAAA;AACN,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACQ,IAAA;AACD,MAAA;AACD,MAAA;AACC,MAAA;AACA,MAAA;AACD,QAAA;AACA,QAAA;AACL,MAAA;AACM,MAAA;AACF,MAAA;AACH,QAAA;AACD,MAAA;AAEM,MAAA;AACN,MAAA;AACI,QAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AAEA,UAAA;AACC,YAAA;AACC,cAAA;AACD,YAAA;AACD,UAAA;AACD,QAAA;AACA,MAAA;AACF,IAAA;AACD,EAAA;AACD;AAWgB;AAGR,EAAA;AACN,IAAA;AACG,IAAA;AACC,IAAA;AACJ,EAAA;AACF;AlDqqWY;AACA;AmDv1WC;AAAuB;AAE5B,EAAA;AAA0B;AAEvB,EAAA;AACV,EAAA;AACD;AAKgB;AACT,EAAA;AACC,EAAA;AACR;AAcgB;AACT,EAAA;AACF,EAAA;AAEE,EAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACP,IAAA;AACO,IAAA;AACR,EAAA;AAEM,EAAA;AACC,IAAA;AACD,IAAA;AACC,IAAA;AACD,IAAA;AACE,IAAA;AACR,EAAA;AAEM,EAAA;AACC,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AAEM,EAAA;AAEE,IAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACE,IAAA;AACP,EAAA;AAEI,EAAA;AACC,IAAA;AACA,IAAA;AACN,IAAA;AACK,MAAA;AAAuB,MAAA;AAEtB,MAAA;AACN,IAAA;AACO,IAAA;AACR,EAAA;AAEO,EAAA;AAAA;AAEN,IAAA;AACI,IAAA;AACH,MAAA;AACD,IAAA;AAAA;AAEA,IAAA;AACM,MAAA;AACN,IAAA;AAAA;AAEA,IAAA;AACC,MAAA;AACD,IAAA;AAAA;AAEK,IAAA;AACJ,MAAA;AAKD,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMM,IAAA;AACL,MAAA;AACM,MAAA;AACA,MAAA;AACF,MAAA;AACE,MAAA;AACN,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAEM,IAAA;AACC,MAAA;AACA,MAAA;AACD,MAAA;AACC,MAAA;AACF,MAAA;AACH,QAAA;AACI,QAAA;AACA,QAAA;AACE,MAAA;AACH,MAAA;AACC,QAAA;AACA,QAAA;AACC,QAAA;AACN,MAAA;AACI,MAAA;AACC,QAAA;AACC,QAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAEA,IAAA;AACO,MAAA;AACD,MAAA;AACL,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAEA,IAAA;AACC,MAAA;AACD,IAAA;AAAA;AAEA,IAAA;AACC,MAAA;AACD,IAAA;AAAA;AAAA;AAAA;AAAA;AAKM,IAAA;AACC9C,MAAAA;AACDA,MAAAA;AACAA,MAAAA;AACD,MAAA;AACHA,QAAAA;AACA,QAAA;AACD,MAAA;AACM,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACNA,MAAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACD;AnD6zWY;AACA;AoDl+WN;AACA,EAAA;AACG,EAAA;AACH,IAAA;AACG,MAAA;AACN,MAAA;AACD,IAAA;AACM,IAAA;AACC,IAAA;AACR,EAAA;AACD;AAGa;AAEJ;AACF,EAAA;AACD,EAAA;AACD,EAAA;AACL;AAOgB;AACP,EAAA;AACF,EAAA;AACA,EAAA;AACAH,EAAAA;AACA,EAAA;AACF,EAAA;AACA,EAAA;AAAgB,EAAA;AACd,EAAA;AACD,IAAA;AACE,IAAA;AACD,IAAA;AACH,MAAA;AACI,QAAA;AACJ,QAAA;AACI,QAAA;AACL,MAAA;AACC,MAAA;AACI,QAAA;AACL,MAAA;AACD,IAAA;AACD,EAAA;AACK,EAAA;AACE,EAAA;AACC,IAAA;AACN,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAGA;AACS,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACAA,EAAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACD,IAAA;AACD,IAAA;AACE,IAAA;AACP,EAAA;AACD;AAMA;AACO,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACN,EAAA;AACK,EAAA;AACC,EAAA;AACA,EAAA;AACD,EAAA;AACI,EAAA;AACN,EAAA;AACE,EAAA;AACG,EAAA;AACV;AAQA;AACS,EAAA;AACF,EAAA;AACA,EAAA;AACAA,EAAAA;AACF,EAAA;AACA,EAAA;AACI,IAAA;AACA,EAAA;AACC,IAAA;AACR,IAAA;AACD,EAAA;AACM,EAAA;AACE,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD,EAAA;AACC,kBAAA;AACJ,EAAA;AACI,EAAA;AACJ,EAAA;AACK,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACC,EAAA;AAEN,EAAA;AACO,IAAA;AACT,EAAA;AACD;ApD68WY;AACA;AqDtlXE;AACT,EAAA;AACM,EAAA;AACJ,EAAA;AACA,EAAA;AACE,EAAA;AACC,EAAA;AACA,EAAA;AACC,EAAA;AACX;AAGgB;AACT,EAAA;AACC,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACC,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACD,IAAA;AACL,EAAA;AACO,EAAA;AACF,IAAA;AACJ,IAAA;AACQ,IAAA;AACR,IAAA;AACA,IAAA;AAEA,IAAA;AACA,IAAA;AACO,IAAA;AACA,IAAA;AACF,MAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,MAAA;AACJ,MAAA;AACD,IAAA;AACQ,IAAA;AACF,MAAA;AACD,MAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACQ,IAAA;AACF,MAAA;AACC,MAAA;AACN,MAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACC,UAAA;AACA,UAAA;AACC,YAAA;AACA,YAAA;AACD,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACA,QAAA;AACA,QAAA;AACC,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AAEA,QAAA;AACC,UAAA;AACA,UAAA;AACC,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACD,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACA,QAAA;AACA,QAAA;AACC,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACD,IAAA;AACA,EAAA;AACF;ArDolXY;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/canvas/index.cjs","sourcesContent":[null,"import type {\n\tCanvasNode,\n\tCanvasNodeStyle,\n\tFanOutTrajectoryOptions,\n\tNodeTransition,\n\tTrajectoryFunction,\n\tTrajectoryPoint,\n\tXYPosition\n} from './types.js';\n\n/**\n * Standard cubic ease-out function (pure math, framework-agnostic).\n */\nexport const defaultEasing = (t: number): number => --t * t * t + 1;\n\n/**\n * Standard linear trajectory: straight line interpolation with linear scale & opacity progression.\n */\nexport const linearTrajectory: TrajectoryFunction = (\n\torigin: XYPosition,\n\ttarget: XYPosition,\n\tprogress: number\n): TrajectoryPoint => {\n\treturn {\n\t\tposition: {\n\t\t\tx: origin.x + (target.x - origin.x) * progress,\n\t\t\ty: origin.y + (target.y - origin.y) * progress\n\t\t},\n\t\tscale: progress,\n\t\topacity: Math.min(1, progress * 1.5)\n\t};\n};\n\n/**\n * Linear position trajectory: pure position interpolation without altering scale or opacity.\n */\nexport const linearPositionTrajectory: TrajectoryFunction = (\n\torigin: XYPosition,\n\ttarget: XYPosition,\n\tprogress: number\n): TrajectoryPoint => {\n\treturn {\n\t\tposition: {\n\t\t\tx: origin.x + (target.x - origin.x) * progress,\n\t\t\ty: origin.y + (target.y - origin.y) * progress\n\t\t}\n\t};\n};\n\n/**\n * Creates a fan-out trajectory where nodes arc outward from origin to target.\n */\nexport function createFanOutTrajectory(options: FanOutTrajectoryOptions = {}): TrajectoryFunction {\n\tconst {\n\t\tcurvature = 0.4,\n\t\tstartScale = 0,\n\t\tendScale = 1,\n\t\tstartOpacity = 0,\n\t\tendOpacity = 1\n\t} = options;\n\n\treturn (\n\t\torigin: XYPosition,\n\t\ttarget: XYPosition,\n\t\tprogress: number,\n\t\tindex: number,\n\t\ttotal: number\n\t): TrajectoryPoint => {\n\t\tconst dx = target.x - origin.x;\n\t\tconst dy = target.y - origin.y;\n\n\t\t// Normalized index from -1 (top) to +1 (bottom)\n\t\tconst normIndex = total > 1 ? (index - (total - 1) / 2) / ((total - 1) / 2) : 0;\n\n\t\t// Perpendicular control point offset to create smooth fan arc\n\t\tconst midX = origin.x + dx * 0.4;\n\t\tconst midY = origin.y + dy * 0.1 - normIndex * Math.abs(dx) * 0.15 * curvature;\n\n\t\t// Quadratic Bezier interpolation: B(t) = (1-t)^2 * P0 + 2(1-t)t * P1 + t^2 * P2\n\t\tconst t = progress;\n\t\tconst oneMinusT = 1 - t;\n\n\t\tconst x = oneMinusT * oneMinusT * origin.x + 2 * oneMinusT * t * midX + t * t * target.x;\n\t\tconst y = oneMinusT * oneMinusT * origin.y + 2 * oneMinusT * t * midY + t * t * target.y;\n\n\t\tconst scale = startScale + (endScale - startScale) * progress;\n\t\tconst opacity = startOpacity + (endOpacity - startOpacity) * progress;\n\n\t\treturn {\n\t\t\tposition: { x, y },\n\t\t\tscale,\n\t\t\topacity\n\t\t};\n\t};\n}\n\n/**\n * Creates a custom quadratic Bezier trajectory with a specified control point resolver.\n */\nexport function createBezierTrajectory(\n\tgetControlPoint: (origin: XYPosition, target: XYPosition) => XYPosition\n): TrajectoryFunction {\n\treturn (origin: XYPosition, target: XYPosition, progress: number): TrajectoryPoint => {\n\t\tconst cp = getControlPoint(origin, target);\n\t\tconst t = progress;\n\t\tconst oneMinusT = 1 - t;\n\n\t\tconst x = oneMinusT * oneMinusT * origin.x + 2 * oneMinusT * t * cp.x + t * t * target.x;\n\t\tconst y = oneMinusT * oneMinusT * origin.y + 2 * oneMinusT * t * cp.y + t * t * target.y;\n\n\t\treturn {\n\t\t\tposition: { x, y },\n\t\t\tscale: progress,\n\t\t\topacity: progress\n\t\t};\n\t};\n}\n\nimport { realClock, type Clock } from './clock.js';\n\n/**\n * Returns a copy of `style` with its opacity set to `opacity`.\n *\n * Supports both CSS strings (`\"color: red; opacity: 0.5;\"`, used by SvelteFlow)\n * and style objects (`{ color: 'red', opacity: 0.5 }`, used by React Flow / xyflow).\n * When `style` is empty, `emptyFormat` decides which representation is produced.\n * Never mutates the input.\n */\nexport function withStyleOpacity(\n\tstyle: CanvasNodeStyle | undefined | null,\n\topacity: number,\n\temptyFormat: 'string' | 'object' = 'string'\n): CanvasNodeStyle {\n\tif (style !== null && typeof style === 'object') {\n\t\treturn { ...style, opacity };\n\t}\n\tif (typeof style === 'string' && style.trim() !== '') {\n\t\tconst rest = style\n\t\t\t.replace(/(^|;)\\s*opacity\\s*:\\s*[^;]*;?/gi, '$1')\n\t\t\t.trim()\n\t\t\t.replace(/;+\\s*$/, '')\n\t\t\t.trim();\n\t\treturn `${rest ? rest + '; ' : ''}opacity: ${opacity};`;\n\t}\n\treturn emptyFormat === 'object' ? { opacity } : `opacity: ${opacity};`;\n}\n\nexport interface MultiNodeTransitionOptions<TNode extends CanvasNode = CanvasNode> {\n\tdefaultDuration?: number;\n\tdefaultEasing?: (t: number) => number;\n\tclock?: Clock;\n\t/**\n\t * Style representation to create for nodes that have no `style` yet when an opacity\n\t * is applied. Defaults to `'string'` (SvelteFlow); pass `'object'` for React Flow.\n\t * Existing styles always keep their representation.\n\t */\n\tstyleFormat?: 'string' | 'object';\n\t/**\n\t * Called on every frame with updated copies of the transitioning nodes, in the same\n\t * order as the `transitions` array. Input nodes are never mutated.\n\t */\n\tonUpdate?: (nodes: TNode[]) => void;\n\t/** Called once all transitions finished, with the final node copies. */\n\tonComplete?: (nodes: TNode[]) => void;\n}\n\n/**\n * Coordinates and animates multiple node transitions simultaneously.\n *\n * The input nodes are treated as immutable: every frame produces updated copies\n * (position, `data.scale` / `data.opacity`, and `style` opacity) which are handed to\n * `onUpdate` and finally `onComplete`. Merge them into your node state by `id`.\n */\nexport function runMultiNodeTransition<TNode extends CanvasNode = CanvasNode>(\n\ttransitions: NodeTransition<TNode>[],\n\toptions: MultiNodeTransitionOptions<TNode> = {}\n): () => void {\n\tif (transitions.length === 0) {\n\t\toptions.onComplete?.([]);\n\t\treturn () => {};\n\t}\n\n\tconst {\n\t\tdefaultDuration = 400,\n\t\tdefaultEasing: easingFunc = defaultEasing,\n\t\tclock = realClock,\n\t\tstyleFormat = 'string',\n\t\tonUpdate,\n\t\tonComplete\n\t} = options;\n\n\tlet isCancelled = false;\n\tconst startTime = clock.now();\n\n\tconst items = transitions.map((tr, i) => ({\n\t\t...tr,\n\t\tindex: tr.index ?? i,\n\t\ttotal: tr.total ?? transitions.length,\n\t\tduration: tr.duration ?? defaultDuration,\n\t\teasing: tr.easing ?? easingFunc,\n\t\tdelay: tr.delay ?? 0,\n\t\ttrajectory: tr.trajectory ?? linearTrajectory\n\t}));\n\n\t// Latest copy of every node; starts out as the untouched inputs.\n\tconst current: TNode[] = items.map((item) => item.node);\n\n\tfunction frame() {\n\t\tif (isCancelled) return;\n\n\t\tconst now = clock.now();\n\t\tlet allCompleted = true;\n\n\t\tfor (let i = 0; i < items.length; i++) {\n\t\t\tconst item = items[i];\n\t\t\tconst elapsed = now - (startTime + item.delay);\n\n\t\t\tif (elapsed < 0) {\n\t\t\t\tallCompleted = false;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Guard zero duration and division by zero (referenced in GOTCHAS.md)\n\t\t\tconst rawProgress = item.duration <= 0 ? 1 : Math.min(elapsed / item.duration, 1);\n\t\t\tconst progress = item.duration <= 0 ? 1 : item.easing(rawProgress);\n\n\t\t\tconst point = item.trajectory(item.from, item.to, progress, item.index, item.total);\n\n\t\t\tconst next: TNode = { ...item.node, position: { ...point.position } };\n\n\t\t\tif (point.scale !== undefined || point.opacity !== undefined) {\n\t\t\t\tconst data: Record<string, any> = { ...(item.node.data ?? {}) };\n\t\t\t\tif (point.scale !== undefined) {\n\t\t\t\t\tdata.scale = point.scale;\n\t\t\t\t}\n\t\t\t\tif (point.opacity !== undefined) {\n\t\t\t\t\tdata.opacity = point.opacity;\n\t\t\t\t\t(next as CanvasNode).style = withStyleOpacity(item.node.style, point.opacity, styleFormat);\n\t\t\t\t}\n\t\t\t\t(next as CanvasNode).data = data;\n\t\t\t}\n\n\t\t\tcurrent[i] = next;\n\n\t\t\tif (rawProgress < 1) {\n\t\t\t\tallCompleted = false;\n\t\t\t}\n\t\t}\n\n\t\tonUpdate?.(current.slice());\n\n\t\tif (!allCompleted) {\n\t\t\tclock.frame().then(frame);\n\t\t} else {\n\t\t\tonComplete?.(current.slice());\n\t\t}\n\t}\n\n\tframe();\n\n\treturn () => {\n\t\tisCancelled = true;\n\t};\n}\n","import dagre from '@dagrejs/dagre';\nimport type {\n\tCanvasEdge,\n\tCanvasNode,\n\tDirectionalDisplacementOptions,\n\tRect,\n\tReflowDisplacementOptions,\n\tXYPosition\n} from './types.js';\n\n/**\n * Calculates a bounding rectangle for a set of nodes if not provided by getNodesBounds.\n */\nexport function computeNodesBoundingBox<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\tdefaultWidth = 200,\n\tdefaultHeight = 60\n): Rect {\n\tif (nodes.length === 0) {\n\t\treturn { x: 0, y: 0, width: 0, height: 0 };\n\t}\n\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\n\tfor (const node of nodes) {\n\t\tconst width = node.measured?.width ?? (node as any).width ?? defaultWidth;\n\t\tconst height = node.measured?.height ?? (node as any).height ?? defaultHeight;\n\n\t\tminX = Math.min(minX, node.position.x);\n\t\tminY = Math.min(minY, node.position.y);\n\t\tmaxX = Math.max(maxX, node.position.x + width);\n\t\tmaxY = Math.max(maxY, node.position.y + height);\n\t}\n\n\treturn {\n\t\tx: minX,\n\t\ty: minY,\n\t\twidth: maxX - minX,\n\t\theight: maxY - minY\n\t};\n}\n\n/**\n * Pure directional displacement calculation.\n * Shifts downstream or surrounding nodes along an axis to clear space for newly inserted child nodes.\n */\nexport function calculateDirectionalDisplacement<TNode extends CanvasNode = CanvasNode>(params: {\n\texistingNodes: TNode[];\n\toriginNode: TNode;\n\tchildNodes: TNode[];\n\tgetNodesBounds?: (nodes: TNode[]) => Rect;\n\toptions?: DirectionalDisplacementOptions;\n}): Map<string, XYPosition> {\n\tconst {\n\t\texistingNodes,\n\t\toriginNode,\n\t\tchildNodes,\n\t\tgetNodesBounds,\n\t\toptions = {}\n\t} = params;\n\n\tconst {\n\t\tdirection = 'right',\n\t\tgap = 40,\n\t\tspreadSiblingLanes = false,\n\t\tsiblingTolerance = 50,\n\t\tminY\n\t} = options;\n\n\tconst displacedMap = new Map<string, XYPosition>();\n\tif (childNodes.length === 0 || existingNodes.length === 0) {\n\t\treturn displacedMap;\n\t}\n\n\tconst childBounds = getNodesBounds\n\t\t? getNodesBounds(childNodes)\n\t\t: computeNodesBoundingBox(childNodes);\n\n\tconst originWidth = originNode.measured?.width ?? (originNode as any).width ?? 200;\n\tconst originHeight = originNode.measured?.height ?? (originNode as any).height ?? 60;\n\n\t// The delta space required along primary axis\n\tconst deltaPrimary =\n\t\tdirection === 'right' || direction === 'left'\n\t\t\t? childBounds.width + gap\n\t\t\t: childBounds.height + gap;\n\n\t// Vertical displacement parameters: upward and downward row clearance\n\tconst deltaUp = Math.max(0, originNode.position.y - childBounds.y + gap);\n\tconst deltaDown = Math.max(\n\t\t0,\n\t\tchildBounds.y + childBounds.height - (originNode.position.y + originHeight) + gap\n\t);\n\n\t// Both above and below displace equally so the row expands symmetrically\n\tconst equalDelta = Math.max(deltaUp, deltaDown);\n\n\tfor (const node of existingNodes) {\n\t\tif (node.id === originNode.id) continue;\n\n\t\tconst isSiblingRow =\n\t\t\tMath.abs(node.position.y - originNode.position.y) < siblingTolerance;\n\n\t\tswitch (direction) {\n\t\t\tcase 'right': {\n\t\t\t\tif (node.position.x > originNode.position.x) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x + deltaPrimary,\n\t\t\t\t\t\ty: node.position.y\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'left': {\n\t\t\t\tif (node.position.x < originNode.position.x) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x - deltaPrimary,\n\t\t\t\t\t\ty: node.position.y\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'down': {\n\t\t\t\tif (node.position.y > originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y + deltaPrimary\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'up': {\n\t\t\t\tif (node.position.y < originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y - deltaPrimary\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'vertical': {\n\t\t\t\tif (isSiblingRow) break;\n\n\t\t\t\tif (node.position.y < originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y - equalDelta\n\t\t\t\t\t});\n\t\t\t\t} else if (node.position.y > originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y + equalDelta\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Sibling lane spreading to avoid overlap among child nodes\n\tif (spreadSiblingLanes && (direction === 'right' || direction === 'left')) {\n\t\tfor (const [nodeId, pos] of displacedMap.entries()) {\n\t\t\tconst node = existingNodes.find((n) => n.id === nodeId);\n\t\t\tif (node && Math.abs(node.position.y - originNode.position.y) < siblingTolerance) {\n\t\t\t\tconst isAbove = node.position.y < originNode.position.y;\n\t\t\t\tdisplacedMap.set(nodeId, {\n\t\t\t\t\tx: pos.x,\n\t\t\t\t\ty: pos.y + (isAbove ? -deltaUp : deltaDown)\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Floor constraint\n\tif (minY !== undefined) {\n\t\tfor (const [nodeId, pos] of displacedMap.entries()) {\n\t\t\tif (pos.y < minY) {\n\t\t\t\tdisplacedMap.set(nodeId, { x: pos.x, y: minY });\n\t\t\t}\n\t\t}\n\t}\n\n\treturn displacedMap;\n}\n\n/**\n * Calculates Dagre reflow displacement preserving the position of an anchor node.\n */\nexport function calculateDagreReflowDisplacement<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(params: {\n\tcurrentNodes: TNode[];\n\tcurrentEdges: TEdge[];\n\tfutureNodes: TNode[];\n\tfutureEdges: TEdge[];\n\toptions: ReflowDisplacementOptions;\n\tgetNodesBounds?: (nodes: TNode[]) => Rect;\n}): Map<string, XYPosition> {\n\tconst {\n\t\tcurrentNodes,\n\t\tfutureNodes,\n\t\tfutureEdges,\n\t\toptions\n\t} = params;\n\n\tconst {\n\t\tanchorNodeId,\n\t\tdirection = 'LR',\n\t\tranksep = 100,\n\t\tnodesep = 25,\n\t\tdefaultNodeWidth = 200,\n\t\tdefaultNodeHeight = 60\n\t} = options;\n\n\tconst anchorNode = currentNodes.find((n) => n.id === anchorNodeId);\n\tif (!anchorNode) return new Map();\n\n\tconst g = new dagre.graphlib.Graph();\n\tg.setDefaultEdgeLabel(() => ({}));\n\tg.setGraph({ rankdir: direction, ranksep, nodesep });\n\n\tconst nodeIds = new Set(futureNodes.map((n) => n.id));\n\n\tfutureNodes.forEach((node) => {\n\t\tconst nodeWidth = node.measured?.width ?? (node as any).width ?? defaultNodeWidth;\n\t\tconst nodeHeight = node.measured?.height ?? (node as any).height ?? defaultNodeHeight;\n\t\tg.setNode(node.id, { width: nodeWidth, height: nodeHeight });\n\t});\n\n\tfutureEdges.forEach((edge) => {\n\t\tif (nodeIds.has(edge.source) && nodeIds.has(edge.target)) {\n\t\t\tg.setEdge(edge.source, edge.target);\n\t\t}\n\t});\n\n\tdagre.layout(g);\n\n\tconst anchorLayoutPos = g.node(anchorNodeId);\n\tif (!anchorLayoutPos) return new Map();\n\n\tconst anchorWidth = anchorNode.measured?.width ?? (anchorNode as any).width ?? defaultNodeWidth;\n\tconst anchorHeight = anchorNode.measured?.height ?? (anchorNode as any).height ?? defaultNodeHeight;\n\n\tconst anchorLayoutUpperLeft = {\n\t\tx: anchorLayoutPos.x - anchorWidth / 2,\n\t\ty: anchorLayoutPos.y - anchorHeight / 2\n\t};\n\n\tconst deltaX = anchorNode.position.x - anchorLayoutUpperLeft.x;\n\tconst deltaY = anchorNode.position.y - anchorLayoutUpperLeft.y;\n\n\tconst displacementMap = new Map<string, XYPosition>();\n\n\tcurrentNodes.forEach((node) => {\n\t\tif (node.id === anchorNodeId) return;\n\n\t\tconst layoutNode = g.node(node.id);\n\t\tif (!layoutNode) return;\n\n\t\tconst nodeWidth = node.measured?.width ?? (node as any).width ?? defaultNodeWidth;\n\t\tconst nodeHeight = node.measured?.height ?? (node as any).height ?? defaultNodeHeight;\n\n\t\tconst upperLeftX = layoutNode.x - nodeWidth / 2 + deltaX;\n\t\tconst upperLeftY = layoutNode.y - nodeHeight / 2 + deltaY;\n\n\t\tif (\n\t\t\tMath.abs(upperLeftX - node.position.x) > 1 ||\n\t\t\tMath.abs(upperLeftY - node.position.y) > 1\n\t\t) {\n\t\t\tdisplacementMap.set(node.id, { x: upperLeftX, y: upperLeftY });\n\t\t}\n\t});\n\n\treturn displacementMap;\n}\n","import type {\n\tCanvasClipboardPayload,\n\tCanvasEdge,\n\tCanvasNode,\n\tXYPosition\n} from './types.js';\n\n/**\n * Checks if an event originated from a text input, textarea, contenteditable element, or rich editor.\n */\nexport function isEventFromTextInput(event: Event | { target: EventTarget | null }): boolean {\n\tconst target = (event as any).target as HTMLElement | null;\n\tif (!target) return false;\n\tif (\n\t\ttarget.tagName === 'INPUT' ||\n\t\ttarget.tagName === 'TEXTAREA' ||\n\t\ttarget.tagName === 'SELECT' ||\n\t\ttarget.isContentEditable ||\n\t\ttarget.closest?.('[contenteditable=\"true\"]') ||\n\t\ttarget.closest?.('.ProseMirror')\n\t) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/**\n * Validates that an object conforms to minimal CanvasNode shape:\n * a string `id` and a `position` whose `x` / `y` are finite numbers.\n */\nexport function isValidCanvasNode(obj: unknown): obj is CanvasNode {\n\tif (typeof obj !== 'object' || obj === null) return false;\n\tconst candidate = obj as { id?: unknown; position?: unknown };\n\tif (typeof candidate.id !== 'string') return false;\n\tconst position = candidate.position as { x?: unknown; y?: unknown } | null | undefined;\n\treturn (\n\t\ttypeof position === 'object' &&\n\t\tposition !== null &&\n\t\ttypeof position.x === 'number' &&\n\t\tNumber.isFinite(position.x) &&\n\t\ttypeof position.y === 'number' &&\n\t\tNumber.isFinite(position.y)\n\t);\n}\n\n/**\n * Validates that an object conforms to minimal CanvasEdge shape (string `id`, `source`, `target`).\n */\nexport function isValidCanvasEdge(obj: unknown): obj is CanvasEdge {\n\tif (typeof obj !== 'object' || obj === null) return false;\n\tconst candidate = obj as { id?: unknown; source?: unknown; target?: unknown };\n\treturn (\n\t\ttypeof candidate.id === 'string' &&\n\t\ttypeof candidate.source === 'string' &&\n\t\ttypeof candidate.target === 'string'\n\t);\n}\n\n/**\n * Checks if a string is a valid HTTP/HTTPS URL.\n */\nexport function isValidUrl(url: string): boolean {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\treturn parsed.protocol === 'http:' || parsed.protocol === 'https:';\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Serializes nodes and optional edges into clipboard JSON payload.\n */\nexport function serializeCanvasNodes<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(nodes: TNode[], edges?: TEdge[]): string {\n\tconst payload: CanvasClipboardPayload<TNode, TEdge> = {\n\t\tversion: 1,\n\t\tnodes,\n\t\tedges\n\t};\n\treturn JSON.stringify(payload);\n}\n\n/**\n * Parses raw JSON string into a CanvasClipboardPayload if valid.\n */\nexport function parseCanvasClipboardData<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(raw: string): CanvasClipboardPayload<TNode, TEdge> | null {\n\ttry {\n\t\tconst parsed = JSON.parse(raw);\n\t\tif (\n\t\t\ttypeof parsed === 'object' &&\n\t\t\tparsed !== null &&\n\t\t\tArray.isArray(parsed.nodes) &&\n\t\t\tparsed.nodes.length > 0 &&\n\t\t\tparsed.nodes.every(isValidCanvasNode)\n\t\t) {\n\t\t\t// Untrusted input: drop malformed edges instead of crashing downstream consumers.\n\t\t\tconst edges = Array.isArray(parsed.edges)\n\t\t\t\t? (parsed.edges as unknown[]).filter(isValidCanvasEdge)\n\t\t\t\t: undefined;\n\t\t\treturn { ...parsed, edges } as CanvasClipboardPayload<TNode, TEdge>;\n\t\t}\n\t} catch {\n\t\t// Not JSON or invalid payload\n\t}\n\treturn null;\n}\n\n/**\n * Generates unique new IDs for pasted nodes and updates edge references accordingly.\n *\n * - Nodes without a valid id / finite numeric position are skipped.\n * - `parentId` (and legacy `parentNode`) references to other pasted nodes are remapped to\n * the new IDs; such children keep their parent-relative position (no extra offset).\n * References to parents outside the pasted set are left unchanged.\n * - Edges are kept only when both endpoints were pasted; a non-array `edges` is ignored.\n */\nexport function remapPastedNodes<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[] | null | undefined = [],\n\toffset: XYPosition = { x: 30, y: 30 },\n\tidGenerator: () => string = () => `node-${Math.random().toString(36).substring(2, 9)}`\n): { nodes: TNode[]; edges: TEdge[] } {\n\tconst validNodes = (Array.isArray(nodes) ? nodes : []).filter(isValidCanvasNode) as TNode[];\n\tconst validEdges = (Array.isArray(edges) ? edges : []).filter(isValidCanvasEdge) as TEdge[];\n\n\tconst dx = Number.isFinite(offset?.x) ? offset.x : 0;\n\tconst dy = Number.isFinite(offset?.y) ? offset.y : 0;\n\n\tconst idMap = new Map<string, string>();\n\tfor (const node of validNodes) {\n\t\tidMap.set(node.id, idGenerator());\n\t}\n\n\tconst newNodes = validNodes.map((node) => {\n\t\tconst next: CanvasNode = {\n\t\t\t...node,\n\t\t\tid: idMap.get(node.id)!,\n\t\t\tposition: {\n\t\t\t\tx: node.position.x + dx,\n\t\t\t\ty: node.position.y + dy\n\t\t\t},\n\t\t\tselected: true\n\t\t};\n\n\t\tlet parentRemapped = false;\n\t\tfor (const key of ['parentId', 'parentNode'] as const) {\n\t\t\tconst parent = (node as CanvasNode)[key];\n\t\t\tif (typeof parent === 'string' && idMap.has(parent)) {\n\t\t\t\tnext[key] = idMap.get(parent)!;\n\t\t\t\tparentRemapped = true;\n\t\t\t}\n\t\t}\n\t\tif (parentRemapped) {\n\t\t\t// Position is relative to the (also pasted, also offset) parent.\n\t\t\tnext.position = { x: node.position.x, y: node.position.y };\n\t\t}\n\n\t\treturn next as TNode;\n\t});\n\n\tconst newEdges = validEdges\n\t\t.filter((e) => idMap.has(e.source) && idMap.has(e.target))\n\t\t.map((edge) => ({\n\t\t\t...edge,\n\t\t\tid: `edge-${Math.random().toString(36).substring(2, 9)}`,\n\t\t\tsource: idMap.get(edge.source)!,\n\t\t\ttarget: idMap.get(edge.target)!,\n\t\t\tselected: false\n\t\t}));\n\n\treturn { nodes: newNodes, edges: newEdges };\n}\n\n/**\n * Repositions nodes so their bounding box origin matches the given target position.\n */\nexport function repositionNodesTo<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\ttargetOrigin: XYPosition\n): TNode[] {\n\tif (nodes.length === 0) return [];\n\tconst minX = Math.min(...nodes.map((n) => n.position.x));\n\tconst minY = Math.min(...nodes.map((n) => n.position.y));\n\tconst deltaX = targetOrigin.x - minX;\n\tconst deltaY = targetOrigin.y - minY;\n\n\treturn nodes.map((n) => ({\n\t\t...n,\n\t\tposition: {\n\t\t\tx: n.position.x + deltaX,\n\t\t\ty: n.position.y + deltaY\n\t\t}\n\t}));\n}\n","import type { CanvasEdge, CanvasNode, Rect } from './types.js';\n\nexport interface CanvasHeightOptions {\n\t/** Smallest height returned (default 320). */\n\tmin?: number;\n\t/** Largest height returned; taller content is scaled/zoomed (default 560). */\n\tmax?: number;\n\t/** Extra padding around the content (default 48). */\n\tpadding?: number;\n}\n\nfunction clampHeight(contentHeight: number, options: CanvasHeightOptions = {}): number {\n\tconst { min = 320, max = 560, padding = 48 } = options;\n\treturn Math.round(Math.min(max, Math.max(min, contentHeight + padding)));\n}\n\n/**\n * Calculates the bounding box extent of all positioned nodes, or `null` for an empty graph.\n */\nexport function layoutBounds<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[]\n): Rect | null {\n\tif (nodes.length === 0) return null;\n\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\n\tfor (const node of nodes) {\n\t\tconst x = node.position.x;\n\t\tconst y = node.position.y;\n\t\tconst width = node.width ?? 180;\n\t\tconst height = node.height ?? 80;\n\n\t\tminX = Math.min(minX, x);\n\t\tminY = Math.min(minY, y);\n\t\tmaxX = Math.max(maxX, x + width);\n\t\tmaxY = Math.max(maxY, y + height);\n\t}\n\n\tif (minX === Infinity) return null;\n\treturn { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n}\n\n/**\n * Returns a clamped container height that fits the layout at 1:1 scale without overflow.\n */\nexport function canvasHeight<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\toptions?: CanvasHeightOptions\n): number {\n\treturn clampHeight(layoutBounds(nodes)?.height ?? 0, options);\n}\n\nconst DEFAULT_RANK_HEIGHT = 80 + 72; // Average node height + default rank separation\nconst NODES_PER_RANK = 2;\n\n/**\n * Estimates the container height to reserve before layout calculation finishes,\n * based on node and edge counts alone. Prevents layout shift (CLS).\n */\nexport function estimateCanvasHeight<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[],\n\toptions?: CanvasHeightOptions\n): number {\n\tconst ranks = Math.max(1, Math.ceil(nodes.length / NODES_PER_RANK));\n\treturn clampHeight(ranks * DEFAULT_RANK_HEIGHT, options);\n}\n\nfunction mix(a: number, b: number, t: number): number {\n\treturn a + (b - a) * t;\n}\n\n/**\n * Interpolates node positions and sizes between two layouts partway at progress `t` (0 = from, 1 = to).\n * Nodes present in both interpolate smoothly; nodes newly added in `to` snap to target.\n */\nexport function blendLayout<TNode extends CanvasNode = CanvasNode>(\n\tfrom: TNode[],\n\tto: TNode[],\n\tt: number\n): TNode[] {\n\tif (t >= 1) return to;\n\n\tconst fromMap = new Map<string, TNode>(from.map((n) => [n.id, n]));\n\n\treturn to.map((target) => {\n\t\tconst origin = fromMap.get(target.id);\n\t\tif (!origin) return target;\n\n\t\treturn {\n\t\t\t...target,\n\t\t\tposition: {\n\t\t\t\tx: mix(origin.position.x, target.position.x, t),\n\t\t\t\ty: mix(origin.position.y, target.position.y, t)\n\t\t\t},\n\t\t\twidth:\n\t\t\t\torigin.width !== undefined && target.width !== undefined\n\t\t\t\t\t? mix(origin.width, target.width, t)\n\t\t\t\t\t: target.width,\n\t\t\theight:\n\t\t\t\torigin.height !== undefined && target.height !== undefined\n\t\t\t\t\t? mix(origin.height, target.height, t)\n\t\t\t\t\t: target.height\n\t\t};\n\t});\n}\n\n/**\n * Filters out nodes that are not touched by any edge.\n *\n * During LLM streaming, nodes often arrive a chunk or token before the edge\n * that connects them. Displaying them immediately causes them to be placed as root\n * nodes only to jump into their correct position once the edge arrives.\n * Hiding orphan nodes during active streaming keeps the layout calm and jitter-free.\n */\nexport function connectedOnly<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[]\n): TNode[] {\n\tif (edges.length === 0) return nodes;\n\n\tconst linkedNodeIds = new Set<string>();\n\tfor (const edge of edges) {\n\t\tlinkedNodeIds.add(edge.source);\n\t\tlinkedNodeIds.add(edge.target);\n\t}\n\n\tconst filtered = nodes.filter((node) => linkedNodeIds.has(node.id));\n\t// If filtering would remove everything, return the original nodes\n\treturn filtered.length > 0 ? filtered : nodes;\n}\n","import { realClock, type Clock } from './clock.js';\nimport { ensureLayout } from './layout.js';\nimport { linearPositionTrajectory, runMultiNodeTransition } from './trajectory.js';\nimport type { CanvasEdge, CanvasNode, IncrementalLayoutOptions, NodeTransition, XYPosition } from './types.js';\nimport { connectedOnly } from './view.js';\n\nexport interface StreamingLayoutManagerOptions {\n\tthrottleMs?: number;\n\tglideDurationMs?: number;\n\tconnectedOnly?: boolean;\n\tclock?: Clock;\n\tlayoutOptions?: IncrementalLayoutOptions;\n\tonLayoutUpdated?: (positions: Map<string, XYPosition>) => void;\n}\n\n/**\n * Manages throttled incremental layout recalculation and smooth tweened node gliding\n * during active streaming (e.g. LLM streaming or graph generation).\n *\n * - `pushStreamUpdate` is a true throttle (leading + trailing edge): the first update\n * lays out immediately, further updates within `throttleMs` are coalesced and the\n * latest one is laid out when the window closes. Layouts therefore keep running at\n * most once per `throttleMs` while a stream is active, instead of being postponed\n * until the stream goes quiet.\n * - Every layout is computed from the graph (respecting `layoutOptions.storedPositions`\n * as pinned/manual coordinates) and each known node glides from its *current*\n * position (the `currentPositions` argument) to its newly computed position.\n */\nexport class StreamingLayoutManager<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n> {\n\tprivate throttleMs: number;\n\tprivate glideDurationMs: number;\n\tprivate connectedOnly: boolean;\n\tprivate clock: Clock;\n\tprivate layoutOptions: IncrementalLayoutOptions;\n\tprivate onLayoutUpdated?: (positions: Map<string, XYPosition>) => void;\n\n\tprivate pendingTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate pendingArgs: [TNode[], TEdge[], Map<string, XYPosition>] | null = null;\n\tprivate activeTransitionCancel: (() => void) | null = null;\n\tprivate targetPositions = new Map<string, XYPosition>();\n\tprivate livePositions = new Map<string, XYPosition>();\n\n\tconstructor(options: StreamingLayoutManagerOptions = {}) {\n\t\tthis.throttleMs = options.throttleMs ?? 200;\n\t\tthis.glideDurationMs = options.glideDurationMs ?? 300;\n\t\tthis.connectedOnly = options.connectedOnly ?? false;\n\t\tthis.clock = options.clock ?? realClock;\n\t\tthis.layoutOptions = options.layoutOptions ?? { direction: 'LR' };\n\t\tthis.onLayoutUpdated = options.onLayoutUpdated;\n\t}\n\n\t/**\n\t * Feed incoming nodes and edges into the streaming manager.\n\t * Throttles rapid streaming bursts (leading + trailing edge) and triggers smooth layout gliding.\n\t */\n\tpublic pushStreamUpdate(\n\t\tnodes: TNode[],\n\t\tedges: TEdge[],\n\t\tcurrentPositions: Map<string, XYPosition>\n\t): void {\n\t\tif (this.pendingTimer !== null) {\n\t\t\t// Inside the throttle window: remember the latest update for the trailing edge.\n\t\t\tthis.pendingArgs = [nodes, edges, currentPositions];\n\t\t\treturn;\n\t\t}\n\n\t\t// Leading edge: lay out immediately and open a throttle window.\n\t\tthis.recalculateAndGlide(nodes, edges, currentPositions);\n\t\tthis.startWindow();\n\t}\n\n\tprivate startWindow(): void {\n\t\tthis.pendingTimer = setTimeout(() => {\n\t\t\tthis.pendingTimer = null;\n\t\t\tconst args = this.pendingArgs;\n\t\t\tthis.pendingArgs = null;\n\t\t\tif (args) {\n\t\t\t\t// Trailing edge: lay out the latest coalesced update and keep throttling.\n\t\t\t\tthis.recalculateAndGlide(...args);\n\t\t\t\tthis.startWindow();\n\t\t\t}\n\t\t}, Math.max(0, this.throttleMs));\n\t}\n\n\t/**\n\t * Forces immediate calculation and cancels pending throttled timers.\n\t */\n\tpublic flush(\n\t\tnodes: TNode[],\n\t\tedges: TEdge[],\n\t\tcurrentPositions: Map<string, XYPosition>\n\t): void {\n\t\tthis.clearPending();\n\t\tthis.recalculateAndGlide(nodes, edges, currentPositions);\n\t}\n\n\t/**\n\t * Latest computed layout targets (the positions nodes are gliding towards).\n\t */\n\tpublic getTargetPositions(): Map<string, XYPosition> {\n\t\treturn new Map(this.targetPositions);\n\t}\n\n\tprivate clearPending(): void {\n\t\tif (this.pendingTimer !== null) {\n\t\t\tclearTimeout(this.pendingTimer);\n\t\t\tthis.pendingTimer = null;\n\t\t}\n\t\tthis.pendingArgs = null;\n\t}\n\n\tprivate recalculateAndGlide(\n\t\tnodes: TNode[],\n\t\tedges: TEdge[],\n\t\tcurrentPositions: Map<string, XYPosition>\n\t): void {\n\t\tconst targetNodes = this.connectedOnly ? connectedOnly(nodes, edges) : nodes;\n\t\tif (targetNodes.length === 0) return;\n\n\t\t// Where nodes are right now: the caller's view, overridden by any glide still in\n\t\t// flight (the caller may not have applied the latest animated frame yet).\n\t\tconst startPositions = new Map<string, XYPosition>(currentPositions);\n\t\tif (this.activeTransitionCancel) {\n\t\t\tfor (const [id, pos] of this.livePositions) {\n\t\t\t\tstartPositions.set(id, pos);\n\t\t\t}\n\t\t\tthis.activeTransitionCancel();\n\t\t\tthis.activeTransitionCancel = null;\n\t\t}\n\n\t\t// Compute fresh layout targets. Only explicitly stored (pinned/manual) positions\n\t\t// are preserved; current positions are the glide *origin*, not the destination.\n\t\tconst layouted = ensureLayout(targetNodes, edges, this.layoutOptions);\n\n\t\tconst nextPositions = new Map<string, XYPosition>();\n\t\tconst transitions: NodeTransition<TNode>[] = [];\n\n\t\tfor (const node of layouted) {\n\t\t\tconst target = { ...node.position };\n\t\t\tnextPositions.set(node.id, target);\n\n\t\t\tconst current = startPositions.get(node.id);\n\t\t\tif (!current) continue; // brand-new node: appears directly at its target\n\n\t\t\tconst distance = Math.hypot(target.x - current.x, target.y - current.y);\n\t\t\tif (distance > 2) {\n\t\t\t\ttransitions.push({\n\t\t\t\t\tnode: { ...node, position: { ...current } },\n\t\t\t\t\tfrom: current,\n\t\t\t\t\tto: target,\n\t\t\t\t\ttrajectory: linearPositionTrajectory,\n\t\t\t\t\tduration: this.glideDurationMs\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tthis.targetPositions = nextPositions;\n\n\t\tif (transitions.length === 0) {\n\t\t\tthis.livePositions = new Map(nextPositions);\n\t\t\tthis.onLayoutUpdated?.(nextPositions);\n\t\t\treturn;\n\t\t}\n\n\t\tconst live = new Map<string, XYPosition>(nextPositions);\n\t\tfor (const trans of transitions) {\n\t\t\tlive.set(trans.node.id, { ...trans.from });\n\t\t}\n\t\tthis.livePositions = live;\n\n\t\tlet finished = false;\n\t\tconst cancel = runMultiNodeTransition(transitions, {\n\t\t\tclock: this.clock,\n\t\t\tonUpdate: (updated) => {\n\t\t\t\tconst frame = new Map<string, XYPosition>(this.livePositions);\n\t\t\t\tfor (const node of updated) {\n\t\t\t\t\tframe.set(node.id, { ...node.position });\n\t\t\t\t}\n\t\t\t\tthis.livePositions = frame;\n\t\t\t\tthis.onLayoutUpdated?.(frame);\n\t\t\t},\n\t\t\tonComplete: () => {\n\t\t\t\tfinished = true;\n\t\t\t\tthis.activeTransitionCancel = null;\n\t\t\t\tthis.livePositions = new Map(nextPositions);\n\t\t\t\tthis.onLayoutUpdated?.(nextPositions);\n\t\t\t}\n\t\t});\n\t\t// A zero-duration glide completes synchronously; don't keep a stale canceller.\n\t\tthis.activeTransitionCancel = finished ? null : cancel;\n\t}\n\n\tpublic destroy(): void {\n\t\tthis.clearPending();\n\t\tif (this.activeTransitionCancel) {\n\t\t\tthis.activeTransitionCancel();\n\t\t\tthis.activeTransitionCancel = null;\n\t\t}\n\t}\n}\n","import { boundsOf, estimateNodeSize } from './layout.js';\nimport type { CanvasNode, CanvasNodeStyle, Rect, XYPosition } from './types.js';\n\n/**\n * Edge (or centre line) used by `alignNodes`.\n * `center` aligns horizontal centres (a vertical line); `middle` aligns vertical centres.\n */\nexport type AlignEdge = 'left' | 'center' | 'right' | 'top' | 'middle' | 'bottom';\n\n/** Axis used by `distributeNodes`. `both` distributes horizontally and vertically. */\nexport type DistributeAxis = 'horizontal' | 'vertical' | 'both';\n\n/**\n * `gaps` leaves equal space between neighbouring nodes, `centres` spaces their centres equally.\n */\nexport type DistributeMode = 'gaps' | 'centres';\n\n/** Dimension(s) copied by `matchSize`. */\nexport type SizeDimension = 'width' | 'height' | 'both';\n\n/**\n * Partial node change produced by the arrange helpers. `position` is in the node's own\n * coordinate space (relative to its `parentId` parent when it has one).\n */\nexport interface CanvasNodeUpdate {\n\tid: string;\n\tposition?: XYPosition;\n\twidth?: number;\n\theight?: number;\n\tzIndex?: number;\n}\n\nexport interface NodeRectOptions {\n\t/** Width used when a node has no measured, explicit or style size and estimation is off. */\n\tdefaultWidth?: number;\n\t/** Height used when a node has no measured, explicit or style size and estimation is off. */\n\tdefaultHeight?: number;\n\t/** Estimate unmeasured node sizes from their label via `estimateNodeSize` (default `true`). */\n\testimate?: boolean;\n}\n\n/** Data key marking a node as locked. See `lockNodes`. */\nexport const LOCKED_DATA_KEY = 'locked';\n/** Data key holding the interaction flags a node had before it was locked. */\nexport const LOCKED_PREVIOUS_FLAGS_KEY = 'lockedPrevious';\n\nfunction parsePx(value: unknown): number | undefined {\n\tif (typeof value === 'number' && Number.isFinite(value)) return value;\n\tif (typeof value === 'string') {\n\t\tconst match = /^\\s*(-?\\d+(?:\\.\\d+)?)(px)?\\s*$/i.exec(value);\n\t\tif (match) return Number(match[1]);\n\t}\n\treturn undefined;\n}\n\n/**\n * Reads a pixel `width` / `height` from a node style, which may be a CSS string (SvelteFlow)\n * or a style object (React Flow). Non-pixel values (`%`, `auto`, ...) are ignored.\n */\nexport function readStyleSize(style: CanvasNodeStyle | undefined | null): {\n\twidth?: number;\n\theight?: number;\n} {\n\tif (!style) return {};\n\tif (typeof style === 'object') {\n\t\treturn { width: parsePx(style.width), height: parsePx(style.height) };\n\t}\n\tconst read = (prop: string) => {\n\t\tconst match = new RegExp(`(?:^|;)\\\\s*${prop}\\\\s*:\\\\s*([^;]+)`, 'i').exec(style);\n\t\treturn match ? parsePx(match[1]) : undefined;\n\t};\n\treturn { width: read('width'), height: read('height') };\n}\n\n/**\n * Returns `style` with its pixel `width` / `height` replaced, keeping its representation\n * (string or object). Only dimensions already present in the style are rewritten, because\n * xyflow applies the node's own `width` / `height` props otherwise.\n */\nexport function withStyleSize(\n\tstyle: CanvasNodeStyle | undefined | null,\n\tsize: { width?: number; height?: number }\n): CanvasNodeStyle | undefined {\n\tif (!style) return style ?? undefined;\n\tif (typeof style === 'object') {\n\t\tconst next: Record<string, any> = { ...style };\n\t\tif (size.width !== undefined && 'width' in next) next.width = size.width;\n\t\tif (size.height !== undefined && 'height' in next) next.height = size.height;\n\t\treturn next;\n\t}\n\tlet next = style;\n\tfor (const prop of ['width', 'height'] as const) {\n\t\tconst value = size[prop];\n\t\tif (value === undefined) continue;\n\t\tnext = next.replace(\n\t\t\tnew RegExp(`((?:^|;)\\\\s*${prop}\\\\s*:\\\\s*)[^;]+`, 'i'),\n\t\t\t`$1${value}px`\n\t\t);\n\t}\n\treturn next;\n}\n\n/**\n * Resolves a node's size: measured DOM size, then explicit `width` / `height`, then a pixel\n * size in `style`, then a label-based estimate (or the defaults when `estimate` is `false`).\n */\nexport function getNodeSize(\n\tnode: CanvasNode,\n\toptions: NodeRectOptions = {}\n): { width: number; height: number } {\n\tconst styleSize = readStyleSize(node.style);\n\tlet width = node.measured?.width ?? node.width ?? styleSize.width;\n\tlet height = node.measured?.height ?? node.height ?? styleSize.height;\n\tif (width === undefined || height === undefined) {\n\t\tconst fallback =\n\t\t\toptions.estimate === false\n\t\t\t\t? { width: options.defaultWidth ?? 200, height: options.defaultHeight ?? 60 }\n\t\t\t\t: estimateNodeSize(node, options.defaultWidth, options.defaultHeight);\n\t\twidth ??= fallback.width;\n\t\theight ??= fallback.height;\n\t}\n\treturn { width, height };\n}\n\n/**\n * Absolute (flow-space) position of a node, following its `parentId` chain.\n * Cycles and missing parents are tolerated.\n */\nexport function getAbsolutePosition(\n\tnode: CanvasNode,\n\tlookup: ReadonlyMap<string, CanvasNode>\n): XYPosition {\n\tlet x = node.position.x;\n\tlet y = node.position.y;\n\tconst seen = new Set<string>([node.id]);\n\tlet parentId = node.parentId;\n\twhile (parentId && !seen.has(parentId)) {\n\t\tconst parent = lookup.get(parentId);\n\t\tif (!parent) break;\n\t\tseen.add(parentId);\n\t\tx += parent.position.x;\n\t\ty += parent.position.y;\n\t\tparentId = parent.parentId;\n\t}\n\treturn { x, y };\n}\n\n/** Builds an id lookup for a node array. */\nexport function createNodeLookup<TNode extends CanvasNode>(nodes: TNode[]): Map<string, TNode> {\n\treturn new Map(nodes.map((n) => [n.id, n]));\n}\n\n/** Absolute flow-space rectangle of a node. */\nexport function getNodeRect(\n\tnode: CanvasNode,\n\tlookup?: ReadonlyMap<string, CanvasNode>,\n\toptions?: NodeRectOptions\n): Rect {\n\tconst { x, y } = node.parentId && lookup ? getAbsolutePosition(node, lookup) : node.position;\n\treturn { x, y, ...getNodeSize(node, options) };\n}\n\n/**\n * Whether a node has been locked with `lockNodes`.\n */\nexport function isLocked(node: CanvasNode | undefined | null): boolean {\n\treturn node?.data?.[LOCKED_DATA_KEY] === true;\n}\n\n/**\n * Locks nodes: marks `data.locked = true` and sets `draggable: false` and `deletable: false`\n * so both SvelteFlow and React Flow stop dragging and deleting them. Nodes stay `selectable`\n * so they can be unlocked again. The previous flags are kept in `data.lockedPrevious` and\n * restored by `unlockNodes`. Returns a new array; untouched nodes keep their identity.\n */\nexport function lockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[] {\n\tconst idSet = new Set(ids);\n\treturn nodes.map((node) => {\n\t\tif (!idSet.has(node.id) || isLocked(node)) return node;\n\t\tconst previous: Record<string, boolean> = {};\n\t\tif (node.draggable !== undefined) previous.draggable = node.draggable;\n\t\tif (node.deletable !== undefined) previous.deletable = node.deletable;\n\t\treturn {\n\t\t\t...node,\n\t\t\tdraggable: false,\n\t\t\tdeletable: false,\n\t\t\tdata: { ...node.data, [LOCKED_DATA_KEY]: true, [LOCKED_PREVIOUS_FLAGS_KEY]: previous }\n\t\t};\n\t});\n}\n\n/**\n * Reverses `lockNodes`, restoring the `draggable` / `deletable` flags the node had before.\n */\nexport function unlockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[] {\n\tconst idSet = new Set(ids);\n\treturn nodes.map((node) => {\n\t\tif (!idSet.has(node.id) || !isLocked(node)) return node;\n\t\tconst {\n\t\t\t[LOCKED_DATA_KEY]: _locked,\n\t\t\t[LOCKED_PREVIOUS_FLAGS_KEY]: previous = {},\n\t\t\t...data\n\t\t} = node.data as Record<string, any>;\n\t\tconst next = { ...node, data } as TNode;\n\t\tdelete next.draggable;\n\t\tdelete next.deletable;\n\t\tif (previous.draggable !== undefined) next.draggable = previous.draggable;\n\t\tif (previous.deletable !== undefined) next.deletable = previous.deletable;\n\t\treturn next;\n\t});\n}\n\n/** Locks the given nodes if any of them is unlocked, otherwise unlocks them all. */\nexport function toggleLockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[] {\n\tconst idSet = new Set(ids);\n\tconst anyUnlocked = nodes.some((n) => idSet.has(n.id) && !isLocked(n));\n\treturn anyUnlocked ? lockNodes(nodes, idSet) : unlockNodes(nodes, idSet);\n}\n\ninterface Item {\n\tnode: CanvasNode;\n\trect: Rect;\n\tlocked: boolean;\n}\n\nfunction collect(nodes: CanvasNode[], ids: Iterable<string>, options?: NodeRectOptions) {\n\tconst lookup = createNodeLookup(nodes);\n\tconst items: Item[] = [];\n\tconst seen = new Set<string>();\n\tfor (const id of ids) {\n\t\tconst node = lookup.get(id);\n\t\tif (!node || seen.has(id)) continue;\n\t\tseen.add(id);\n\t\titems.push({ node, rect: getNodeRect(node, lookup, options), locked: isLocked(node) });\n\t}\n\treturn { lookup, items };\n}\n\n/**\n * Converts absolute target positions into node-relative `position` updates. When a parent\n * moves in the same operation, children are made relative to the parent's new position.\n */\nfunction toPositionUpdates(\n\tlookup: Map<string, CanvasNode>,\n\ttargets: Map<string, XYPosition>\n): CanvasNodeUpdate[] {\n\tconst updates: CanvasNodeUpdate[] = [];\n\tfor (const [id, abs] of targets) {\n\t\tconst node = lookup.get(id)!;\n\t\tlet position = abs;\n\t\tif (node.parentId && lookup.has(node.parentId)) {\n\t\t\tconst parent = lookup.get(node.parentId)!;\n\t\t\tconst parentAbs = targets.get(parent.id) ?? getAbsolutePosition(parent, lookup);\n\t\t\tposition = { x: abs.x - parentAbs.x, y: abs.y - parentAbs.y };\n\t\t}\n\t\tif (position.x !== node.position.x || position.y !== node.position.y) {\n\t\t\tupdates.push({ id, position });\n\t\t}\n\t}\n\treturn updates;\n}\n\nexport interface AlignNodesOptions extends NodeRectOptions {\n\t/**\n\t * What to align to: the selection bounding box (default) or a fixed rectangle.\n\t * Locked nodes never move but still count towards the selection bounds, so they act as anchors.\n\t */\n\tto?: 'selection' | Rect;\n}\n\n/**\n * Aligns nodes to a shared edge or centre line of their bounding box (Freeform's\n * Align Left / Center / Right / Top / Middle / Bottom). Needs at least two nodes unless\n * `options.to` is a rectangle. Locked nodes are not moved.\n */\nexport function alignNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\tedge: AlignEdge,\n\toptions: AlignNodesOptions = {}\n): CanvasNodeUpdate[] {\n\tconst { lookup, items } = collect(nodes, ids, options);\n\tconst fixed = typeof options.to === 'object' ? options.to : null;\n\tif (items.length === 0 || (!fixed && items.length < 2)) return [];\n\tconst bounds = fixed ?? boundsOf(items.map((i) => i.rect))!;\n\n\tconst targets = new Map<string, XYPosition>();\n\tfor (const { node, rect, locked } of items) {\n\t\tif (locked) continue;\n\t\tlet { x, y } = rect;\n\t\tswitch (edge) {\n\t\t\tcase 'left':\n\t\t\t\tx = bounds.x;\n\t\t\t\tbreak;\n\t\t\tcase 'center':\n\t\t\t\tx = bounds.x + bounds.width / 2 - rect.width / 2;\n\t\t\t\tbreak;\n\t\t\tcase 'right':\n\t\t\t\tx = bounds.x + bounds.width - rect.width;\n\t\t\t\tbreak;\n\t\t\tcase 'top':\n\t\t\t\ty = bounds.y;\n\t\t\t\tbreak;\n\t\t\tcase 'middle':\n\t\t\t\ty = bounds.y + bounds.height / 2 - rect.height / 2;\n\t\t\t\tbreak;\n\t\t\tcase 'bottom':\n\t\t\t\ty = bounds.y + bounds.height - rect.height;\n\t\t\t\tbreak;\n\t\t}\n\t\ttargets.set(node.id, { x, y });\n\t}\n\treturn toPositionUpdates(lookup, targets);\n}\n\nexport interface DistributeNodesOptions extends NodeRectOptions {\n\t/** Explicit gap between neighbours for `gaps` mode. Default: spread evenly between the outer nodes. */\n\tgap?: number;\n}\n\nfunction distributeAxis(\n\titems: Item[],\n\thorizontal: boolean,\n\tmode: DistributeMode,\n\tgapOverride: number | undefined,\n\ttargets: Map<string, XYPosition>\n) {\n\tconst start = (r: Rect) => (horizontal ? r.x : r.y);\n\tconst size = (r: Rect) => (horizontal ? r.width : r.height);\n\tconst sorted = [...items].sort(\n\t\t(a, b) => start(a.rect) + size(a.rect) / 2 - (start(b.rect) + size(b.rect) / 2)\n\t);\n\tconst first = sorted[0].rect;\n\tconst last = sorted[sorted.length - 1].rect;\n\tconst n = sorted.length;\n\tconst set = (item: Item, value: number) => {\n\t\tif (item.locked) return;\n\t\tconst current = targets.get(item.node.id) ?? { x: item.rect.x, y: item.rect.y };\n\t\ttargets.set(item.node.id, horizontal ? { ...current, x: value } : { ...current, y: value });\n\t};\n\n\tif (mode === 'centres') {\n\t\tconst c0 = start(first) + size(first) / 2;\n\t\tconst c1 = start(last) + size(last) / 2;\n\t\tconst step = (c1 - c0) / (n - 1);\n\t\tsorted.forEach((item, i) => set(item, c0 + step * i - size(item.rect) / 2));\n\t\treturn;\n\t}\n\n\tconst span = start(last) + size(last) - start(first);\n\tconst total = sorted.reduce((sum, item) => sum + size(item.rect), 0);\n\tconst gap = gapOverride ?? (span - total) / (n - 1);\n\tlet cursor = start(first);\n\tfor (const item of sorted) {\n\t\tset(item, cursor);\n\t\tcursor += size(item.rect) + gap;\n\t}\n}\n\n/**\n * Distributes nodes along an axis (Freeform's Distribute Horizontally / Vertically / Evenly).\n * The outermost nodes stay put and the others are spaced with equal gaps (`gaps`, default)\n * or equally spaced centres (`centres`). Needs at least three nodes (two with an explicit\n * `gap`). Locked nodes keep their place in the order but are not moved.\n */\nexport function distributeNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\taxis: DistributeAxis,\n\tmode: DistributeMode = 'gaps',\n\toptions: DistributeNodesOptions = {}\n): CanvasNodeUpdate[] {\n\tconst { lookup, items } = collect(nodes, ids, options);\n\tconst minimum = options.gap !== undefined && mode === 'gaps' ? 2 : 3;\n\tif (items.length < minimum) return [];\n\tconst targets = new Map<string, XYPosition>();\n\tif (axis !== 'vertical') distributeAxis(items, true, mode, options.gap, targets);\n\tif (axis !== 'horizontal') distributeAxis(items, false, mode, options.gap, targets);\n\treturn toPositionUpdates(lookup, targets);\n}\n\nexport interface MatchSizeOptions extends NodeRectOptions {\n\t/**\n\t * Size source: the `largest` (default) or `smallest` node, the `first` id given,\n\t * or a specific node id.\n\t */\n\treference?: 'largest' | 'smallest' | 'first' | { id: string };\n\t/** Keep each node's centre in place instead of its top-left corner (default `false`). */\n\tkeepCentre?: boolean;\n}\n\n/**\n * Makes nodes the same width, height or both (Freeform's Make Same Size). Returns size\n * updates (plus position updates with `keepCentre`). Locked nodes are not resized but can\n * be the reference.\n */\nexport function matchSize(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\tdimension: SizeDimension,\n\toptions: MatchSizeOptions = {}\n): CanvasNodeUpdate[] {\n\tconst { items } = collect(nodes, ids, options);\n\tif (items.length < 2) return [];\n\tconst reference = options.reference ?? 'largest';\n\n\tconst pick = (dim: 'width' | 'height'): number => {\n\t\tif (typeof reference === 'object') {\n\t\t\tconst item = items.find((i) => i.node.id === reference.id);\n\t\t\tif (item) return item.rect[dim];\n\t\t}\n\t\tif (reference === 'first') return items[0].rect[dim];\n\t\tconst values = items.map((i) => i.rect[dim]);\n\t\treturn reference === 'smallest' ? Math.min(...values) : Math.max(...values);\n\t};\n\tconst width = dimension !== 'height' ? pick('width') : undefined;\n\tconst height = dimension !== 'width' ? pick('height') : undefined;\n\n\tconst updates: CanvasNodeUpdate[] = [];\n\tfor (const { node, rect, locked } of items) {\n\t\tif (locked) continue;\n\t\tconst update: CanvasNodeUpdate = { id: node.id };\n\t\tif (width !== undefined && width !== rect.width) update.width = width;\n\t\tif (height !== undefined && height !== rect.height) update.height = height;\n\t\tif (update.width === undefined && update.height === undefined) continue;\n\t\tif (options.keepCentre) {\n\t\t\tupdate.position = {\n\t\t\t\tx: node.position.x - ((update.width ?? rect.width) - rect.width) / 2,\n\t\t\t\ty: node.position.y - ((update.height ?? rect.height) - rect.height) / 2\n\t\t\t};\n\t\t}\n\t\tupdates.push(update);\n\t}\n\treturn updates;\n}\n\n/** Z-order operation. */\nexport type ZOrderOperation = 'bringForward' | 'sendBackward' | 'bringToFront' | 'sendToBack';\n\n/**\n * Restacks nodes (Bring Forward / Send Backward / Bring to Front / Send to Back).\n *\n * Both SvelteFlow and React Flow stack nodes by `zIndex` (missing = 0) and, for equal\n * `zIndex`, by array order. This computes that effective order for the siblings that share\n * the targets' parent, applies the operation, and returns `zIndex` updates assigning each\n * sibling its new rank. The array order is left alone, because xyflow requires parents to\n * precede their children. Locked nodes are not restacked. \"Forward\" moves past the next\n * sibling the node overlaps, falling back to the next sibling when nothing overlaps.\n */\nexport function reorderNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\toperation: ZOrderOperation,\n\toptions: NodeRectOptions = {}\n): CanvasNodeUpdate[] {\n\tconst lookup = createNodeLookup(nodes);\n\tconst targetIds = new Set(\n\t\t[...ids].filter((id) => lookup.has(id) && !isLocked(lookup.get(id)))\n\t);\n\tif (targetIds.size === 0) return [];\n\n\t// Restack each sibling group (nodes sharing a parent) independently.\n\tconst groups = new Map<string, CanvasNode[]>();\n\tfor (const node of nodes) {\n\t\tconst key = node.parentId ?? '';\n\t\tif (!groups.has(key)) groups.set(key, []);\n\t\tgroups.get(key)!.push(node);\n\t}\n\n\tconst updates: CanvasNodeUpdate[] = [];\n\tfor (const siblings of groups.values()) {\n\t\tif (!siblings.some((n) => targetIds.has(n.id))) continue;\n\t\tconst order = siblings\n\t\t\t.map((node, index) => ({ node, index, z: typeof node.zIndex === 'number' ? node.zIndex : 0 }))\n\t\t\t.sort((a, b) => a.z - b.z || a.index - b.index)\n\t\t\t.map((entry) => entry.node);\n\t\tconst isTarget = (n: CanvasNode) => targetIds.has(n.id);\n\t\tlet next: CanvasNode[];\n\n\t\tif (operation === 'bringToFront') {\n\t\t\tnext = [...order.filter((n) => !isTarget(n)), ...order.filter(isTarget)];\n\t\t} else if (operation === 'sendToBack') {\n\t\t\tnext = [...order.filter(isTarget), ...order.filter((n) => !isTarget(n))];\n\t\t} else {\n\t\t\tnext = [...order];\n\t\t\tconst forward = operation === 'bringForward';\n\t\t\tconst rects = new Map(next.map((n) => [n.id, getNodeRect(n, lookup, options)]));\n\t\t\tconst intersects = (a: Rect, b: Rect) =>\n\t\t\t\ta.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;\n\t\t\t// Walk from the end we are moving towards so adjacent targets keep their relative order.\n\t\t\tconst indices = next.map((_, i) => i);\n\t\t\tif (forward) indices.reverse();\n\t\t\tfor (const i of indices) {\n\t\t\t\tconst node = next[i];\n\t\t\t\tif (!isTarget(node)) continue;\n\t\t\t\tconst step = forward ? 1 : -1;\n\t\t\t\tlet j = i + step;\n\t\t\t\tlet swapWith = -1;\n\t\t\t\tlet firstNonTarget = -1;\n\t\t\t\twhile (j >= 0 && j < next.length) {\n\t\t\t\t\tif (!isTarget(next[j])) {\n\t\t\t\t\t\tif (firstNonTarget === -1) firstNonTarget = j;\n\t\t\t\t\t\tif (intersects(rects.get(node.id)!, rects.get(next[j].id)!)) {\n\t\t\t\t\t\t\tswapWith = j;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tj += step;\n\t\t\t\t}\n\t\t\t\tif (swapWith === -1) swapWith = firstNonTarget;\n\t\t\t\tif (swapWith === -1) continue;\n\t\t\t\tnext.splice(i, 1);\n\t\t\t\tnext.splice(swapWith, 0, node);\n\t\t\t}\n\t\t}\n\n\t\tif (next.every((node, i) => node === order[i])) continue;\n\t\tnext.forEach((node, rank) => {\n\t\t\tconst current = typeof node.zIndex === 'number' ? node.zIndex : 0;\n\t\t\tif (current !== rank) updates.push({ id: node.id, zIndex: rank });\n\t\t});\n\t}\n\treturn updates;\n}\n\n/**\n * Applies `CanvasNodeUpdate`s to a node array, returning a new array. Unchanged nodes keep\n * their identity. Size updates set `width` / `height` and rewrite pixel sizes already present\n * in `style` (string or object). An existing `measured` size is updated too, so geometry\n * helpers see the new size before xyflow re-measures the node.\n */\nexport function applyNodeUpdates<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tupdates: CanvasNodeUpdate[]\n): TNode[] {\n\tif (updates.length === 0) return nodes;\n\tconst byId = new Map<string, CanvasNodeUpdate>();\n\tfor (const update of updates) {\n\t\tbyId.set(update.id, { ...byId.get(update.id), ...update });\n\t}\n\treturn nodes.map((node) => {\n\t\tconst update = byId.get(node.id);\n\t\tif (!update) return node;\n\t\tconst next = { ...node } as TNode;\n\t\tif (update.position) next.position = { ...update.position };\n\t\tif (update.zIndex !== undefined) (next as CanvasNode).zIndex = update.zIndex;\n\t\tif (update.width !== undefined || update.height !== undefined) {\n\t\t\tif (update.width !== undefined) next.width = update.width;\n\t\t\tif (update.height !== undefined) next.height = update.height;\n\t\t\tif (node.style) next.style = withStyleSize(node.style, update);\n\t\t\tif (node.measured) {\n\t\t\t\tnext.measured = {\n\t\t\t\t\t...node.measured,\n\t\t\t\t\t...(update.width !== undefined ? { width: update.width } : {}),\n\t\t\t\t\t...(update.height !== undefined ? { height: update.height } : {})\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\treturn next;\n\t});\n}\n\n/**\n * Inverse of a set of updates against the current nodes, for undo. Restores the previous\n * position / size / zIndex of every node an update touches.\n */\nexport function invertNodeUpdates(\n\tnodes: CanvasNode[],\n\tupdates: CanvasNodeUpdate[]\n): CanvasNodeUpdate[] {\n\tconst lookup = createNodeLookup(nodes);\n\tconst inverse: CanvasNodeUpdate[] = [];\n\tfor (const update of updates) {\n\t\tconst node = lookup.get(update.id);\n\t\tif (!node) continue;\n\t\tconst back: CanvasNodeUpdate = { id: node.id };\n\t\tif (update.position) back.position = { ...node.position };\n\t\tif (update.zIndex !== undefined) back.zIndex = typeof node.zIndex === 'number' ? node.zIndex : 0;\n\t\tif (update.width !== undefined || update.height !== undefined) {\n\t\t\tconst size = getNodeSize(node);\n\t\t\tif (update.width !== undefined) back.width = size.width;\n\t\t\tif (update.height !== undefined) back.height = size.height;\n\t\t}\n\t\tinverse.push(back);\n\t}\n\treturn inverse;\n}\n","import { boundsOf } from './layout.js';\nimport { createNodeLookup, getNodeRect, type CanvasNodeUpdate, type NodeRectOptions } from './arrange.js';\nimport type { CanvasNode, Rect, XYPosition } from './types.js';\n\n/**\n * A guide line to render while dragging, in flow coordinates.\n * `edge` and `centre` guides are alignment lines; `spacing` guides mark equal gaps.\n * `measure` lines (with a distance `label`) and dashed `extension` lines come from the\n * distance measuring helpers (`measureBetween`, `measureSelection`).\n */\nexport interface GuideLine {\n\torientation: 'vertical' | 'horizontal';\n\tkind: 'edge' | 'centre' | 'spacing' | 'measure' | 'extension';\n\tx1: number;\n\ty1: number;\n\tx2: number;\n\ty2: number;\n\t/** Text drawn at the middle of the line (measuring distances). */\n\tlabel?: string;\n}\n\n/**\n * Snaps `value` to a grid with the given `spacing` and `origin`. A spacing of 0 or less\n * (or a non-finite one) leaves the value unchanged.\n *\n * Ported from vectorcraft-main/crates/geom/src/snap.rs (MIT OR Apache-2.0, © ArtCraft Team\n * and contributors). See THIRD_PARTY_NOTICES.md.\n */\nexport function snapToGrid(value: number, spacing: number, origin = 0): number {\n\tif (!(spacing > 0) || !Number.isFinite(spacing)) return value;\n\treturn Math.round((value - origin) / spacing) * spacing + origin;\n}\n\nexport interface SnapGuidesOptions {\n\t/** Current viewport zoom; the threshold is in screen pixels and divided by it (default 1). */\n\tzoom?: number;\n\t/** Snap distance in screen pixels (default 6). */\n\tthreshold?: number;\n\t/** Snap edges to other shapes' edges (default `true`). */\n\tedges?: boolean;\n\t/** Snap centres to other shapes' centres (default `true`). */\n\tcentres?: boolean;\n\t/** Snap to equal spacing between neighbouring shapes in the same row / column (default `true`). */\n\tspacing?: boolean;\n\t/**\n\t * Grid spacing in flow units (default 0, off). An axis that does not snap to a shape snaps\n\t * its top-left (drag) or moving edge (resize) to the grid; grid snaps draw no guides.\n\t */\n\tgridSize?: number;\n\t/** Grid origin in flow units (default `{ x: 0, y: 0 }`). */\n\tgridOrigin?: XYPosition;\n}\n\nexport interface SnapResult {\n\t/** Snapped top-left of the moving rectangle. */\n\tposition: XYPosition;\n\t/** Offset applied to the input rectangle (`position - rect`). */\n\toffset: XYPosition;\n\t/** Guide segments to draw for the snap that was applied. */\n\tguides: GuideLine[];\n\t/** Whether x snapped to a shape (grid snaps do not count). */\n\tsnappedX: boolean;\n\tsnappedY: boolean;\n}\n\ninterface AxisEntries {\n\tvalues: Float64Array;\n\trects: Rect[];\n}\n\nfunction buildAxis(entries: { value: number; rect: Rect }[]): AxisEntries {\n\tentries.sort((a, b) => a.value - b.value);\n\treturn {\n\t\tvalues: Float64Array.from(entries.map((e) => e.value)),\n\t\trects: entries.map((e) => e.rect)\n\t};\n}\n\n/** First index whose value is >= `v`. */\nfunction lowerBound(values: Float64Array, v: number): number {\n\tlet lo = 0;\n\tlet hi = values.length;\n\twhile (lo < hi) {\n\t\tconst mid = (lo + hi) >>> 1;\n\t\tif (values[mid] < v) lo = mid + 1;\n\t\telse hi = mid;\n\t}\n\treturn lo;\n}\n\n/**\n * Pre-sorted snap targets for a set of static rectangles. Build it once when a drag starts\n * and reuse it on every pointer move; each query is a binary search, so snapping stays\n * cheap with hundreds or thousands of shapes.\n */\nexport interface GuideIndex {\n\trects: Rect[];\n\tedgeX: AxisEntries;\n\tcentreX: AxisEntries;\n\tedgeY: AxisEntries;\n\tcentreY: AxisEntries;\n}\n\n/** Builds a `GuideIndex` from the rectangles the moving shape may snap to. */\nexport function createGuideIndex(rects: Rect[]): GuideIndex {\n\tconst edgeX: { value: number; rect: Rect }[] = [];\n\tconst centreX: { value: number; rect: Rect }[] = [];\n\tconst edgeY: { value: number; rect: Rect }[] = [];\n\tconst centreY: { value: number; rect: Rect }[] = [];\n\tfor (const rect of rects) {\n\t\tedgeX.push({ value: rect.x, rect }, { value: rect.x + rect.width, rect });\n\t\tcentreX.push({ value: rect.x + rect.width / 2, rect });\n\t\tedgeY.push({ value: rect.y, rect }, { value: rect.y + rect.height, rect });\n\t\tcentreY.push({ value: rect.y + rect.height / 2, rect });\n\t}\n\treturn {\n\t\trects,\n\t\tedgeX: buildAxis(edgeX),\n\t\tcentreX: buildAxis(centreX),\n\t\tedgeY: buildAxis(edgeY),\n\t\tcentreY: buildAxis(centreY)\n\t};\n}\n\n/** Nearest indexed value within `tolerance` of `v`, as a signed delta, or `null`. */\nfunction nearest(axis: AxisEntries, v: number, tolerance: number): number | null {\n\tconst { values } = axis;\n\tlet best: number | null = null;\n\tfor (let i = lowerBound(values, v - tolerance); i < values.length && values[i] <= v + tolerance; i++) {\n\t\tconst delta = values[i] - v;\n\t\tif (best === null || Math.abs(delta) < Math.abs(best)) best = delta;\n\t}\n\treturn best;\n}\n\n/** Rectangles whose indexed value equals `v` (within `eps`). */\nfunction matches(axis: AxisEntries, v: number, eps: number): Rect[] {\n\tconst { values, rects } = axis;\n\tconst out: Rect[] = [];\n\tfor (let i = lowerBound(values, v - eps); i < values.length && values[i] <= v + eps; i++) {\n\t\tout.push(rects[i]);\n\t}\n\treturn out;\n}\n\ninterface SpacingCandidate {\n\tdelta: number;\n\tguides: GuideLine[];\n}\n\n/**\n * Equal-spacing candidates along one axis. Looks at shapes in the same row (horizontal) or\n * column (vertical) as the moving rectangle and proposes: centring between the nearest\n * neighbours, or repeating the gap a neighbour has to its own neighbour.\n */\nfunction spacingCandidate(\n\trects: Rect[],\n\tmoving: Rect,\n\thorizontal: boolean,\n\ttolerance: number\n): SpacingCandidate | null {\n\tconst start = (r: Rect) => (horizontal ? r.x : r.y);\n\tconst end = (r: Rect) => (horizontal ? r.x + r.width : r.y + r.height);\n\tconst crossStart = (r: Rect) => (horizontal ? r.y : r.x);\n\tconst crossEnd = (r: Rect) => (horizontal ? r.y + r.height : r.x + r.width);\n\tconst size = end(moving) - start(moving);\n\tconst inLane = (r: Rect, lane: Rect) => crossStart(r) < crossEnd(lane) && crossEnd(r) > crossStart(lane);\n\n\tconst lane = rects.filter((r) => inLane(r, moving));\n\tif (lane.length === 0) return null;\n\n\tlet before: Rect | null = null;\n\tlet after: Rect | null = null;\n\tfor (const r of lane) {\n\t\tif (end(r) <= start(moving) + tolerance && (!before || end(r) > end(before))) before = r;\n\t\tif (start(r) >= end(moving) - tolerance && (!after || start(r) < start(after))) after = r;\n\t}\n\n\tconst neighbourOf = (r: Rect, forward: boolean): Rect | null => {\n\t\tlet best: Rect | null = null;\n\t\tfor (const other of lane) {\n\t\t\tif (other === r || !inLane(other, r)) continue;\n\t\t\tif (forward ? start(other) >= end(r) : end(other) <= start(r)) {\n\t\t\t\tif (!best || (forward ? start(other) < start(best) : end(other) > end(best))) best = other;\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t};\n\n\tconst crossMid = (a: Rect, b: Rect) =>\n\t\t(Math.max(crossStart(a), crossStart(b)) + Math.min(crossEnd(a), crossEnd(b))) / 2;\n\tconst segment = (from: number, to: number, cross: number): GuideLine =>\n\t\thorizontal\n\t\t\t? { orientation: 'horizontal', kind: 'spacing', x1: from, y1: cross, x2: to, y2: cross }\n\t\t\t: { orientation: 'vertical', kind: 'spacing', x1: cross, y1: from, x2: cross, y2: to };\n\n\tconst candidates: { pos: number; guides: (pos: number) => GuideLine[] }[] = [];\n\tif (before && after) {\n\t\tconst b = before;\n\t\tconst a = after;\n\t\tconst pos = (end(b) + start(a) - size) / 2;\n\t\tif (pos >= end(b)) {\n\t\t\tcandidates.push({\n\t\t\t\tpos,\n\t\t\t\tguides: (p) => [\n\t\t\t\t\tsegment(end(b), p, crossMid(b, moving)),\n\t\t\t\t\tsegment(p + size, start(a), crossMid(a, moving))\n\t\t\t\t]\n\t\t\t});\n\t\t}\n\t}\n\tif (before) {\n\t\tconst b = before;\n\t\tconst prev = neighbourOf(b, false);\n\t\tif (prev) {\n\t\t\tconst gap = start(b) - end(prev);\n\t\t\tcandidates.push({\n\t\t\t\tpos: end(b) + gap,\n\t\t\t\tguides: (p) => [segment(end(prev), start(b), crossMid(prev, b)), segment(end(b), p, crossMid(b, moving))]\n\t\t\t});\n\t\t}\n\t}\n\tif (after) {\n\t\tconst a = after;\n\t\tconst next = neighbourOf(a, true);\n\t\tif (next) {\n\t\t\tconst gap = start(next) - end(a);\n\t\t\tcandidates.push({\n\t\t\t\tpos: start(a) - gap - size,\n\t\t\t\tguides: (p) => [segment(p + size, start(a), crossMid(a, moving)), segment(end(a), start(next), crossMid(a, next))]\n\t\t\t});\n\t\t}\n\t}\n\n\tlet best: SpacingCandidate | null = null;\n\tfor (const c of candidates) {\n\t\tconst delta = c.pos - start(moving);\n\t\tif (Math.abs(delta) <= tolerance && (!best || Math.abs(delta) < Math.abs(best.delta))) {\n\t\t\tbest = { delta, guides: c.guides(c.pos) };\n\t\t}\n\t}\n\treturn best;\n}\n\n/**\n * Computes smart-guide snapping for a moving rectangle (a dragged node or a selection's\n * bounding box) against other shapes. Edges snap to edges and centres to centres within\n * `threshold` screen pixels (converted to flow units via `zoom`). Each axis snaps\n * independently to its nearest candidate. Returns the snapped position and the guides to draw.\n */\nexport function computeSnapGuides(\n\tmoving: Rect,\n\ttargets: Rect[] | GuideIndex,\n\toptions: SnapGuidesOptions = {}\n): SnapResult {\n\tconst index = Array.isArray(targets) ? createGuideIndex(targets) : targets;\n\tconst zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;\n\tconst tolerance = (options.threshold ?? 6) / zoom;\n\tconst useEdges = options.edges !== false;\n\tconst useCentres = options.centres !== false;\n\tconst useSpacing = options.spacing !== false;\n\tconst eps = 0.5 / zoom;\n\n\tconst snapAxis = (horizontal: boolean) => {\n\t\tconst start = horizontal ? moving.x : moving.y;\n\t\tconst size = horizontal ? moving.width : moving.height;\n\t\tconst edgeAxis = horizontal ? index.edgeX : index.edgeY;\n\t\tconst centreAxis = horizontal ? index.centreX : index.centreY;\n\t\tlet delta: number | null = null;\n\t\tconst consider = (d: number | null) => {\n\t\t\tif (d !== null && (delta === null || Math.abs(d) < Math.abs(delta))) delta = d;\n\t\t};\n\t\tif (useEdges) {\n\t\t\tconsider(nearest(edgeAxis, start, tolerance));\n\t\t\tconsider(nearest(edgeAxis, start + size, tolerance));\n\t\t}\n\t\tif (useCentres) consider(nearest(centreAxis, start + size / 2, tolerance));\n\t\tconst spacing = useSpacing ? spacingCandidate(index.rects, moving, horizontal, tolerance) : null;\n\t\tif (spacing && (delta === null || Math.abs(spacing.delta) < Math.abs(delta) - eps)) {\n\t\t\treturn { delta: spacing.delta, spacing };\n\t\t}\n\t\treturn { delta: delta as number | null, spacing: null };\n\t};\n\n\tconst sx = snapAxis(true);\n\tconst sy = snapAxis(false);\n\tconst grid = options.gridSize ?? 0;\n\tconst gridDelta = (v: number, origin: number) => snapToGrid(v, grid, origin) - v;\n\tconst dx = sx.delta ?? (grid > 0 ? gridDelta(moving.x, options.gridOrigin?.x ?? 0) : 0);\n\tconst dy = sy.delta ?? (grid > 0 ? gridDelta(moving.y, options.gridOrigin?.y ?? 0) : 0);\n\tconst snapped: Rect = { x: moving.x + dx, y: moving.y + dy, width: moving.width, height: moving.height };\n\tconst guides: GuideLine[] = [];\n\n\tconst alignmentGuides = (horizontal: boolean) => {\n\t\tconst start = horizontal ? snapped.x : snapped.y;\n\t\tconst size = horizontal ? snapped.width : snapped.height;\n\t\tconst lines: { value: number; kind: 'edge' | 'centre'; axis: AxisEntries }[] = [];\n\t\tif (useEdges) {\n\t\t\tconst axis = horizontal ? index.edgeX : index.edgeY;\n\t\t\tlines.push({ value: start, kind: 'edge', axis }, { value: start + size, kind: 'edge', axis });\n\t\t}\n\t\tif (useCentres) {\n\t\t\tlines.push({ value: start + size / 2, kind: 'centre', axis: horizontal ? index.centreX : index.centreY });\n\t\t}\n\t\tfor (const line of lines) {\n\t\t\tconst aligned = matches(line.axis, line.value, eps);\n\t\t\tif (aligned.length === 0) continue;\n\t\t\tconst span = boundsOf([snapped, ...aligned])!;\n\t\t\tguides.push(\n\t\t\t\thorizontal\n\t\t\t\t\t? { orientation: 'vertical', kind: line.kind, x1: line.value, y1: span.y, x2: line.value, y2: span.y + span.height }\n\t\t\t\t\t: { orientation: 'horizontal', kind: line.kind, x1: span.x, y1: line.value, x2: span.x + span.width, y2: line.value }\n\t\t\t);\n\t\t}\n\t};\n\n\tif (sx.delta !== null) {\n\t\tif (sx.spacing) guides.push(...shiftGuides(sx.spacing.guides, 0, dy));\n\t\telse alignmentGuides(true);\n\t}\n\tif (sy.delta !== null) {\n\t\tif (sy.spacing) guides.push(...shiftGuides(sy.spacing.guides, dx, 0));\n\t\telse alignmentGuides(false);\n\t}\n\n\treturn {\n\t\tposition: { x: snapped.x, y: snapped.y },\n\t\toffset: { x: dx, y: dy },\n\t\tguides,\n\t\tsnappedX: sx.delta !== null,\n\t\tsnappedY: sy.delta !== null\n\t};\n}\n\n/** Spacing guides are computed before the other axis snaps; shift their cross coordinate to match. */\nfunction shiftGuides(guides: GuideLine[], dx: number, dy: number): GuideLine[] {\n\treturn guides.map((g) =>\n\t\tg.orientation === 'horizontal'\n\t\t\t? { ...g, y1: g.y1 + dy, y2: g.y2 + dy }\n\t\t\t: { ...g, x1: g.x1 + dx, x2: g.x2 + dx }\n\t);\n}\n\n/** Ids of the given nodes plus every descendant (children move with their parent). */\nexport function collectDescendantIds(nodes: CanvasNode[], ids: Iterable<string>): Set<string> {\n\tconst result = new Set(ids);\n\tlet grew = true;\n\twhile (grew) {\n\t\tgrew = false;\n\t\tfor (const node of nodes) {\n\t\t\tif (node.parentId && result.has(node.parentId) && !result.has(node.id)) {\n\t\t\t\tresult.add(node.id);\n\t\t\t\tgrew = true;\n\t\t\t}\n\t\t}\n\t}\n\treturn result;\n}\n\n/**\n * Builds a `GuideIndex` of every node except the dragged ones and their descendants.\n * Call once at drag start and pass it to `snapNodeDrag` on every move.\n */\nexport function createNodeGuideIndex(\n\tnodes: CanvasNode[],\n\tdraggingIds: Iterable<string>,\n\toptions: NodeRectOptions & { filter?: (node: CanvasNode) => boolean } = {}\n): GuideIndex {\n\tconst exclude = collectDescendantIds(nodes, draggingIds);\n\tconst lookup = createNodeLookup(nodes);\n\tconst rects: Rect[] = [];\n\tfor (const node of nodes) {\n\t\tif (exclude.has(node.id) || node.hidden === true) continue;\n\t\tif (options.filter && !options.filter(node)) continue;\n\t\trects.push(getNodeRect(node, lookup, options));\n\t}\n\treturn createGuideIndex(rects);\n}\n\nexport interface SnapNodeDragOptions extends SnapGuidesOptions, NodeRectOptions {\n\t/** Index from `createNodeGuideIndex`, reused across drag frames. Built on demand when omitted. */\n\tindex?: GuideIndex;\n}\n\nexport interface SnapNodeDragResult extends SnapResult {\n\t/** Position updates moving every dragged node by `offset` (node-relative coordinates). */\n\tupdates: CanvasNodeUpdate[];\n}\n\n/**\n * Snaps a drag of one or more nodes. The dragged nodes' bounding box is snapped against all\n * other nodes; the resulting offset is applied to each dragged node. Dragged nodes whose\n * parent is also dragged are skipped, because they move with the parent.\n */\nexport function snapNodeDrag(\n\tnodes: CanvasNode[],\n\tdraggingIds: Iterable<string>,\n\toptions: SnapNodeDragOptions = {}\n): SnapNodeDragResult {\n\tconst ids = new Set(draggingIds);\n\tconst lookup = createNodeLookup(nodes);\n\tconst dragged = [...ids].map((id) => lookup.get(id)).filter((n): n is CanvasNode => !!n);\n\tconst empty: SnapNodeDragResult = {\n\t\tposition: { x: 0, y: 0 },\n\t\toffset: { x: 0, y: 0 },\n\t\tguides: [],\n\t\tsnappedX: false,\n\t\tsnappedY: false,\n\t\tupdates: []\n\t};\n\tif (dragged.length === 0) return empty;\n\n\tconst bounds = boundsOf(dragged.map((n) => getNodeRect(n, lookup, options)))!;\n\tconst index = options.index ?? createNodeGuideIndex(nodes, ids, options);\n\tconst result = computeSnapGuides(bounds, index, options);\n\tconst { x: dx, y: dy } = result.offset;\n\tconst updates: CanvasNodeUpdate[] = [];\n\tif (dx !== 0 || dy !== 0) {\n\t\tfor (const node of dragged) {\n\t\t\tif (node.parentId && ids.has(node.parentId)) continue;\n\t\t\tupdates.push({ id: node.id, position: { x: node.position.x + dx, y: node.position.y + dy } });\n\t\t}\n\t}\n\treturn { ...result, updates };\n}\n","import { centre } from './layout.js';\nimport type { Position, Rect, Side, XYPosition } from './types.js';\n\n/**\n * Connection line styles supported by both SvelteFlow and React Flow\n * (`connectionLineType` / edge `type`).\n */\nexport type CanvasConnectionLineType = 'default' | 'straight' | 'step' | 'smoothstep' | 'simplebezier';\n\nexport const CONNECTION_LINE_TYPES: readonly CanvasConnectionLineType[] = [\n\t'default',\n\t'straight',\n\t'step',\n\t'smoothstep',\n\t'simplebezier'\n];\n\n/** Maps a handle `Position` to the placement `Side` vocabulary used by `placeBeside`. */\nexport function positionToSide(position: Position): Side {\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn 'above';\n\t\tcase 'bottom':\n\t\t\treturn 'below';\n\t\tdefault:\n\t\t\treturn position;\n\t}\n}\n\n/** Maps a placement `Side` to the handle `Position` vocabulary used by xyflow. */\nexport function sideToPosition(side: Side): Position {\n\tswitch (side) {\n\t\tcase 'above':\n\t\t\treturn 'top';\n\t\tcase 'below':\n\t\t\treturn 'bottom';\n\t\tdefault:\n\t\t\treturn side;\n\t}\n}\n\n/** The handle position facing the other way. */\nexport function oppositePosition(position: Position): Position {\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn 'bottom';\n\t\tcase 'bottom':\n\t\t\treturn 'top';\n\t\tcase 'left':\n\t\t\treturn 'right';\n\t\tdefault:\n\t\t\treturn 'left';\n\t}\n}\n\n/** Midpoint of a rectangle's side, where a centred handle sits. */\nexport function handlePoint(rect: Rect, position: Position): XYPosition {\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn { x: rect.x + rect.width / 2, y: rect.y };\n\t\tcase 'bottom':\n\t\t\treturn { x: rect.x + rect.width / 2, y: rect.y + rect.height };\n\t\tcase 'left':\n\t\t\treturn { x: rect.x, y: rect.y + rect.height / 2 };\n\t\tdefault:\n\t\t\treturn { x: rect.x + rect.width, y: rect.y + rect.height / 2 };\n\t}\n}\n\nexport interface ConnectorSnapTarget {\n\tid: string;\n\trect: Rect;\n\t/** Sides that can accept a connection (default: all four). */\n\tpositions?: Position[];\n}\n\nexport interface ConnectorSnapOptions {\n\t/** Viewport zoom; `radius` is in screen pixels and divided by it (default 1). */\n\tzoom?: number;\n\t/** How far outside a shape the pointer may be and still attach, in screen pixels (default 24). */\n\tradius?: number;\n\t/** Node ids that cannot be attached to, e.g. the connection's source node. */\n\texcludeIds?: Iterable<string>;\n\t/**\n\t * `handle` snaps to the side's midpoint (a centred handle, default). `edge` snaps to the\n\t * closest point along that side, for floating edges.\n\t */\n\tmode?: 'handle' | 'edge';\n}\n\nexport interface ConnectorSnap {\n\tnodeId: string;\n\tposition: Position;\n\tside: Side;\n\t/** Attachment point in flow coordinates. */\n\tpoint: XYPosition;\n\t/** Distance from the pointer to `point`, in flow units. */\n\tdistance: number;\n}\n\n/**\n * Finds the shape side a connection line should attach to for a pointer (flow coordinates).\n * Shapes the pointer is inside win over shapes it is merely near; among those, the closest\n * allowed side wins. Returns `null` when nothing is within `radius`.\n */\nexport function findConnectorSnap(\n\tpointer: XYPosition,\n\ttargets: ConnectorSnapTarget[],\n\toptions: ConnectorSnapOptions = {}\n): ConnectorSnap | null {\n\tconst zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;\n\tconst radius = (options.radius ?? 24) / zoom;\n\tconst exclude = new Set(options.excludeIds ?? []);\n\tconst all: Position[] = ['top', 'right', 'bottom', 'left'];\n\n\tlet best: (ConnectorSnap & { inside: boolean; rank: number }) | null = null;\n\tfor (const target of targets) {\n\t\tif (exclude.has(target.id)) continue;\n\t\tconst { rect } = target;\n\t\t// Cheap rejection: pointer outside the rect grown by the radius.\n\t\tif (\n\t\t\tpointer.x < rect.x - radius ||\n\t\t\tpointer.x > rect.x + rect.width + radius ||\n\t\t\tpointer.y < rect.y - radius ||\n\t\t\tpointer.y > rect.y + rect.height + radius\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst inside =\n\t\t\tpointer.x >= rect.x &&\n\t\t\tpointer.x <= rect.x + rect.width &&\n\t\t\tpointer.y >= rect.y &&\n\t\t\tpointer.y <= rect.y + rect.height;\n\n\t\tfor (const position of target.positions ?? all) {\n\t\t\tconst point =\n\t\t\t\toptions.mode === 'edge' ? closestPointOnSide(rect, position, pointer) : handlePoint(rect, position);\n\t\t\t// Rank sides by distance from the pointer to the side line, so a pointer near the\n\t\t\t// middle of a wide shape attaches to the side it is closest to.\n\t\t\tconst sidePoint = closestPointOnSide(rect, position, pointer);\n\t\t\tconst rank = Math.hypot(pointer.x - sidePoint.x, pointer.y - sidePoint.y);\n\t\t\tconst distance = Math.hypot(pointer.x - point.x, pointer.y - point.y);\n\t\t\tif (!inside && rank > radius) continue;\n\t\t\tconst better =\n\t\t\t\t!best ||\n\t\t\t\t(inside && !best.inside) ||\n\t\t\t\t(inside === best.inside && rank < best.rank);\n\t\t\tif (better) {\n\t\t\t\tbest = { nodeId: target.id, position, side: positionToSide(position), point, distance, inside, rank };\n\t\t\t}\n\t\t}\n\t}\n\tif (!best) return null;\n\tconst { inside: _inside, rank: _rank, ...snap } = best;\n\treturn snap;\n}\n\nfunction closestPointOnSide(rect: Rect, position: Position, p: XYPosition): XYPosition {\n\tconst clampX = Math.min(rect.x + rect.width, Math.max(rect.x, p.x));\n\tconst clampY = Math.min(rect.y + rect.height, Math.max(rect.y, p.y));\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn { x: clampX, y: rect.y };\n\t\tcase 'bottom':\n\t\t\treturn { x: clampX, y: rect.y + rect.height };\n\t\tcase 'left':\n\t\t\treturn { x: rect.x, y: clampY };\n\t\tdefault:\n\t\t\treturn { x: rect.x + rect.width, y: clampY };\n\t}\n}\n\n/**\n * Picks the facing sides for a connection between two shapes, the way Freeform attaches\n * connection lines: along the axis with the larger centre offset.\n */\nexport function nearestHandlePositions(\n\tsource: Rect,\n\ttarget: Rect\n): { source: Position; target: Position } {\n\tconst a = centre(source);\n\tconst b = centre(target);\n\tconst dx = b.x - a.x;\n\tconst dy = b.y - a.y;\n\tif (Math.abs(dx) >= Math.abs(dy)) {\n\t\treturn dx >= 0 ? { source: 'right', target: 'left' } : { source: 'left', target: 'right' };\n\t}\n\treturn dy >= 0 ? { source: 'bottom', target: 'top' } : { source: 'top', target: 'bottom' };\n}\n","import { boundsOf } from './layout.js';\nimport { createNodeLookup, getNodeRect, type NodeRectOptions } from './arrange.js';\nimport type { CanvasNode, Rect } from './types.js';\n\n/**\n * Viewport transform in xyflow's convention: `screen = flow * zoom + (x, y)`.\n */\nexport interface CanvasViewport {\n\tx: number;\n\ty: number;\n\tzoom: number;\n}\n\n/**\n * A named, saved view of the canvas (Freeform \"Scenes\").\n */\nexport interface CanvasScene {\n\tid: string;\n\tname: string;\n\tviewport: CanvasViewport;\n\t/** Optional nodes the scene is about, e.g. to re-fit the scene after they move. */\n\tnodeIds?: string[];\n\t/**\n\t * A provider transition effect (see `@pixerate/editor/effects`) played when the canvas moves\n\t * to this scene. Kept, and ignored, without an effects provider.\n\t */\n\ttransition?: CanvasSceneTransition;\n}\n\n/** A transition effect into a scene (see `CanvasScene.transition`). */\nexport interface CanvasSceneTransition {\n\t/** The provider's transition effect id. */\n\teffect: string;\n\tprovider?: string;\n\tname?: string;\n\t/** Control values (over the effect's defaults). */\n\tvalues: Record<string, number | string | boolean | readonly number[]>;\n\t/** Length in ms (default 600). */\n\tduration?: number;\n}\n\nexport interface CanvasScenesState {\n\tscenes: readonly CanvasScene[];\n\t/** Id of the scene last shown, or `null`. */\n\tcurrentId: string | null;\n\t/** Index of the current scene, or `-1`. */\n\tcurrentIndex: number;\n}\n\nexport interface CanvasScenesOptions {\n\tinitialScenes?: CanvasScene[];\n\t/** Id generator for new scenes (default: `crypto.randomUUID` with a counter fallback). */\n\tcreateId?: () => string;\n\t/** Whether `next` / `previous` wrap around at the ends (default `false`). */\n\twrap?: boolean;\n\tonChange?: (state: CanvasScenesState) => void;\n}\n\nexport interface SerializedCanvasScenes {\n\tversion: 1;\n\tscenes: CanvasScene[];\n}\n\nlet fallbackCounter = 0;\nfunction defaultId(): string {\n\tif (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n\t\treturn crypto.randomUUID();\n\t}\n\tfallbackCounter += 1;\n\treturn `scene-${Date.now().toString(36)}-${fallbackCounter}`;\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n\treturn typeof value === 'number' && Number.isFinite(value);\n}\n\n/** Validates the shape of a scene. */\nexport function isValidCanvasScene(value: unknown): value is CanvasScene {\n\tif (typeof value !== 'object' || value === null) return false;\n\tconst scene = value as Record<string, any>;\n\tconst vp = scene.viewport;\n\treturn (\n\t\ttypeof scene.id === 'string' &&\n\t\ttypeof scene.name === 'string' &&\n\t\ttypeof vp === 'object' &&\n\t\tvp !== null &&\n\t\tisFiniteNumber(vp.x) &&\n\t\tisFiniteNumber(vp.y) &&\n\t\tisFiniteNumber(vp.zoom) &&\n\t\tvp.zoom > 0 &&\n\t\t(scene.nodeIds === undefined ||\n\t\t\t(Array.isArray(scene.nodeIds) && scene.nodeIds.every((id: unknown) => typeof id === 'string')))\n\t);\n}\n\nfunction cloneScene(scene: CanvasScene): CanvasScene {\n\tconst copy: CanvasScene = {\n\t\tid: scene.id,\n\t\tname: scene.name,\n\t\tviewport: { x: scene.viewport.x, y: scene.viewport.y, zoom: scene.viewport.zoom }\n\t};\n\tif (scene.nodeIds) copy.nodeIds = [...scene.nodeIds];\n\tconst t = scene.transition;\n\tif (t && typeof t.effect === 'string' && t.effect) copy.transition = JSON.parse(JSON.stringify(t)) as CanvasSceneTransition;\n\treturn copy;\n}\n\n/**\n * Parses serialized scenes (a JSON string, a `{ version, scenes }` object or a bare array).\n * Invalid entries and duplicate ids are dropped.\n */\nexport function parseCanvasScenes(input: string | unknown): CanvasScene[] {\n\tlet value: unknown = input;\n\tif (typeof input === 'string') {\n\t\ttry {\n\t\t\tvalue = JSON.parse(input);\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\tconst list = Array.isArray(value)\n\t\t? value\n\t\t: typeof value === 'object' && value !== null && Array.isArray((value as any).scenes)\n\t\t\t? (value as any).scenes\n\t\t\t: [];\n\tconst seen = new Set<string>();\n\tconst scenes: CanvasScene[] = [];\n\tfor (const entry of list) {\n\t\tif (!isValidCanvasScene(entry) || seen.has(entry.id)) continue;\n\t\tseen.add(entry.id);\n\t\tscenes.push(cloneScene(entry));\n\t}\n\treturn scenes;\n}\n\n/**\n * Headless, framework-agnostic store of named viewports. Snapshots returned by `getState()`\n * are cached and only replaced on change, so it can back React's `useSyncExternalStore`.\n */\nexport class CanvasScenesStore {\n\tprivate scenes: CanvasScene[];\n\tprivate currentId: string | null = null;\n\tprivate cached: CanvasScenesState;\n\tprivate listeners = new Set<(state: CanvasScenesState) => void>();\n\tprivate readonly createId: () => string;\n\tprivate readonly onChange?: (state: CanvasScenesState) => void;\n\twrap: boolean;\n\n\tconstructor(options: CanvasScenesOptions = {}) {\n\t\tthis.scenes = parseCanvasScenes(options.initialScenes ?? []);\n\t\tthis.createId = options.createId ?? defaultId;\n\t\tthis.wrap = options.wrap ?? false;\n\t\tthis.onChange = options.onChange;\n\t\tthis.cached = this.snapshot();\n\t}\n\n\tprivate snapshot(): CanvasScenesState {\n\t\tconst currentIndex = this.currentId ? this.scenes.findIndex((s) => s.id === this.currentId) : -1;\n\t\treturn {\n\t\t\tscenes: this.scenes,\n\t\t\tcurrentId: currentIndex === -1 ? null : this.currentId,\n\t\t\tcurrentIndex\n\t\t};\n\t}\n\n\tprivate commit(scenes: CanvasScene[] = this.scenes) {\n\t\tthis.scenes = scenes;\n\t\tif (this.currentId && !scenes.some((s) => s.id === this.currentId)) this.currentId = null;\n\t\tthis.cached = this.snapshot();\n\t\tthis.onChange?.(this.cached);\n\t\tfor (const listener of this.listeners) listener(this.cached);\n\t}\n\n\tgetState(): CanvasScenesState {\n\t\treturn this.cached;\n\t}\n\n\tgetScene(id: string): CanvasScene | undefined {\n\t\treturn this.scenes.find((s) => s.id === id);\n\t}\n\n\tsubscribe(listener: (state: CanvasScenesState) => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.listeners.delete(listener);\n\t\t};\n\t}\n\n\t/** First unused \"Scene N\" name. */\n\tnextDefaultName(): string {\n\t\tconst names = new Set(this.scenes.map((s) => s.name));\n\t\tlet n = this.scenes.length + 1;\n\t\twhile (names.has(`Scene ${n}`)) n += 1;\n\t\treturn `Scene ${n}`;\n\t}\n\n\t/**\n\t * Adds a scene (appended, or inserted at `index`) and makes it current.\n\t */\n\tadd(input: { viewport: CanvasViewport; name?: string; nodeIds?: string[]; id?: string }, index?: number): CanvasScene {\n\t\tconst scene = cloneScene({\n\t\t\tid: input.id && !this.getScene(input.id) ? input.id : this.createId(),\n\t\t\tname: input.name?.trim() || this.nextDefaultName(),\n\t\t\tviewport: input.viewport,\n\t\t\tnodeIds: input.nodeIds\n\t\t});\n\t\tif (!isValidCanvasScene(scene)) throw new Error('[CanvasScenesStore] Invalid scene viewport.');\n\t\tconst scenes = [...this.scenes];\n\t\tconst at = index === undefined ? scenes.length : Math.max(0, Math.min(scenes.length, index));\n\t\tscenes.splice(at, 0, scene);\n\t\tthis.currentId = scene.id;\n\t\tthis.commit(scenes);\n\t\treturn scene;\n\t}\n\n\t/** Patches a scene's name, viewport or node ids. Returns the updated scene. */\n\tupdate(id: string, patch: Partial<Omit<CanvasScene, 'id'>>): CanvasScene | undefined {\n\t\tconst index = this.scenes.findIndex((s) => s.id === id);\n\t\tif (index === -1) return undefined;\n\t\tconst current = this.scenes[index];\n\t\tconst next = cloneScene({\n\t\t\t...current,\n\t\t\t...patch,\n\t\t\tname: patch.name !== undefined ? patch.name.trim() || current.name : current.name,\n\t\t\tviewport: patch.viewport ?? current.viewport,\n\t\t\tid\n\t\t});\n\t\tif (!isValidCanvasScene(next)) throw new Error('[CanvasScenesStore] Invalid scene viewport.');\n\t\tconst scenes = [...this.scenes];\n\t\tscenes[index] = next;\n\t\tthis.commit(scenes);\n\t\treturn next;\n\t}\n\n\trename(id: string, name: string): CanvasScene | undefined {\n\t\treturn this.update(id, { name });\n\t}\n\n\t/** Moves a scene to `toIndex` (clamped). */\n\tmove(id: string, toIndex: number): void {\n\t\tconst from = this.scenes.findIndex((s) => s.id === id);\n\t\tif (from === -1) return;\n\t\tconst to = Math.max(0, Math.min(this.scenes.length - 1, Math.trunc(toIndex)));\n\t\tif (from === to) return;\n\t\tconst scenes = [...this.scenes];\n\t\tconst [scene] = scenes.splice(from, 1);\n\t\tscenes.splice(to, 0, scene);\n\t\tthis.commit(scenes);\n\t}\n\n\t/** Reorders scenes to match `ids`; unknown ids are ignored and missing scenes keep their relative order at the end. */\n\treorder(ids: string[]): void {\n\t\tconst byId = new Map(this.scenes.map((s) => [s.id, s]));\n\t\tconst ordered: CanvasScene[] = [];\n\t\tfor (const id of ids) {\n\t\t\tconst scene = byId.get(id);\n\t\t\tif (scene) {\n\t\t\t\tordered.push(scene);\n\t\t\t\tbyId.delete(id);\n\t\t\t}\n\t\t}\n\t\tthis.commit([...ordered, ...this.scenes.filter((s) => byId.has(s.id))]);\n\t}\n\n\tremove(id: string): void {\n\t\tif (!this.getScene(id)) return;\n\t\tthis.commit(this.scenes.filter((s) => s.id !== id));\n\t}\n\n\tclear(): void {\n\t\tthis.currentId = null;\n\t\tthis.commit([]);\n\t}\n\n\t/** Marks a scene as current and returns it. */\n\tgoTo(id: string): CanvasScene | undefined {\n\t\tconst scene = this.getScene(id);\n\t\tif (!scene) return undefined;\n\t\tif (this.currentId !== id) {\n\t\t\tthis.currentId = id;\n\t\t\tthis.commit();\n\t\t}\n\t\treturn scene;\n\t}\n\n\tprivate step(direction: 1 | -1): CanvasScene | undefined {\n\t\tconst count = this.scenes.length;\n\t\tif (count === 0) return undefined;\n\t\tconst index = this.cached.currentIndex;\n\t\tlet next: number;\n\t\tif (index === -1) next = direction === 1 ? 0 : count - 1;\n\t\telse next = index + direction;\n\t\tif (next < 0 || next >= count) {\n\t\t\tif (!this.wrap) return undefined;\n\t\t\tnext = (next + count) % count;\n\t\t}\n\t\treturn this.goTo(this.scenes[next].id);\n\t}\n\n\t/** Shows the next scene. Returns `undefined` at the end unless `wrap` is on. */\n\tnext(): CanvasScene | undefined {\n\t\treturn this.step(1);\n\t}\n\n\t/** Shows the previous scene. Returns `undefined` at the start unless `wrap` is on. */\n\tprevious(): CanvasScene | undefined {\n\t\treturn this.step(-1);\n\t}\n\n\ttoJSON(): SerializedCanvasScenes {\n\t\treturn { version: 1, scenes: this.scenes.map(cloneScene) };\n\t}\n\n\tserialize(): string {\n\t\treturn JSON.stringify(this.toJSON());\n\t}\n\n\t/** Replaces all scenes from serialized data (see `parseCanvasScenes`). */\n\tload(input: string | unknown): void {\n\t\tthis.currentId = null;\n\t\tthis.commit(parseCanvasScenes(input));\n\t}\n}\n\nexport function createCanvasScenesStore(options?: CanvasScenesOptions): CanvasScenesStore {\n\treturn new CanvasScenesStore(options);\n}\n\nexport interface FitViewportOptions {\n\t/** Padding around the content as a fraction of the content size per side (default 0.1, like xyflow). */\n\tpadding?: number;\n\tminZoom?: number;\n\tmaxZoom?: number;\n}\n\n/**\n * Viewport that fits `rect` into a container of `size`, centred (xyflow `fitView` maths).\n */\nexport function fitViewportToRect(\n\trect: Rect,\n\tsize: { width: number; height: number },\n\toptions: FitViewportOptions = {}\n): CanvasViewport {\n\tconst { padding = 0.1, minZoom = 0.1, maxZoom = 2 } = options;\n\tconst paddedWidth = Math.max(rect.width * (1 + padding * 2), 1);\n\tconst paddedHeight = Math.max(rect.height * (1 + padding * 2), 1);\n\tconst raw = Math.min(size.width / paddedWidth, size.height / paddedHeight);\n\tconst zoom = Math.min(maxZoom, Math.max(minZoom, raw));\n\tconst cx = rect.x + rect.width / 2;\n\tconst cy = rect.y + rect.height / 2;\n\treturn { x: size.width / 2 - cx * zoom, y: size.height / 2 - cy * zoom, zoom };\n}\n\n/**\n * Viewport that fits the given nodes (all nodes when `ids` is omitted), or `null` when none match.\n */\nexport function fitViewportToNodes(\n\tnodes: CanvasNode[],\n\tsize: { width: number; height: number },\n\tids?: Iterable<string>,\n\toptions: FitViewportOptions & NodeRectOptions = {}\n): CanvasViewport | null {\n\tconst lookup = createNodeLookup(nodes);\n\tconst wanted = ids ? new Set(ids) : null;\n\tconst rects = nodes\n\t\t.filter((n) => (wanted ? wanted.has(n.id) : true))\n\t\t.map((n) => getNodeRect(n, lookup, options));\n\tconst bounds = boundsOf(rects);\n\treturn bounds ? fitViewportToRect(bounds, size, options) : null;\n}\n","/**\n * Shape nodes and Freeform-style shape boolean operations (Unite / Intersect / Subtract /\n * Exclude / Divide) on canvas nodes.\n *\n * A shape node is any node whose `data.shape` is a `ShapeDescription`. Its geometry is\n * relative to the node box, so moving or resizing the node moves / resizes the shape.\n */\nimport {\n\tcreateNodeLookup,\n\tgetAbsolutePosition,\n\tgetNodeSize,\n\tisLocked,\n\twithStyleSize,\n\tLOCKED_DATA_KEY,\n\tLOCKED_PREVIOUS_FLAGS_KEY,\n\ttype NodeRectOptions\n} from './arrange.js';\nimport {\n\tdividePolygons,\n\tmultiPolygonBounds,\n\tpolygonBoolean,\n\ttype MultiPolygon,\n\ttype PolygonBooleanOperation,\n\ttype PolygonFillRule,\n\ttype PolygonRing\n} from './polygonBoolean.js';\nimport {\n\tgetShapeSvg,\n\tisShapeDescription,\n\tpolygonToSvgPath,\n\tshapeToPolygon,\n\ttype PathShape,\n\ttype ShapeDescription,\n\ttype ShapeSize\n} from './shapeGeometry.js';\nimport {\n\tbezierPathBounds,\n\tbezierPathToSvg,\n\ttoBezierPath,\n\ttransformBezierPath,\n\ttype AffineMatrix,\n\ttype BezierPath\n} from './pathops/bezier.js';\nimport { dividePaths, pathBoolean, type PathOpsReport } from './pathops/boolean.js';\nimport { offsetPath, outlineStroke, type StrokeCap, type StrokeJoin } from './pathops/stroke.js';\nimport type { CanvasNode, Point } from './types.js';\n\n/** Data key holding a node's shape description. */\nexport const SHAPE_DATA_KEY = 'shape';\n\n/** Node type suggested for shape nodes (register `ShapeNode` under it). */\nexport const SHAPE_NODE_TYPE = 'shape';\n\n/** Freeform's shape operations. `exclude` is xor; `divide` splits into every region. */\nexport type ShapeOperation = 'unite' | 'intersect' | 'subtract' | 'exclude' | 'divide';\n\nexport const SHAPE_OPERATIONS: readonly ShapeOperation[] = ['unite', 'intersect', 'subtract', 'exclude', 'divide'];\n\nconst BOOLEAN_FOR: Record<Exclude<ShapeOperation, 'divide'>, PolygonBooleanOperation> = {\n\tunite: 'union',\n\tintersect: 'intersection',\n\tsubtract: 'difference',\n\texclude: 'xor'\n};\n\n/** Whether a node carries a supported `data.shape`. */\nexport function isShapeNode(node: CanvasNode | undefined | null): boolean {\n\treturn !!node && isShapeDescription(node.data?.[SHAPE_DATA_KEY]);\n}\n\n/** The node's shape description, or `null`. */\nexport function getNodeShape(node: CanvasNode | undefined | null): ShapeDescription | null {\n\tconst shape = node?.data?.[SHAPE_DATA_KEY];\n\treturn isShapeDescription(shape) ? shape : null;\n}\n\nexport interface ShapeNodePolygonOptions extends NodeRectOptions {\n\t/** Curve flattening tolerance in flow units (default 0.25). */\n\ttolerance?: number;\n}\n\n/**\n * A shape node's outline in absolute flow coordinates (parent offsets, size and rotation\n * applied), or `null` for non-shape nodes.\n */\nexport function shapeNodeToPolygon(\n\tnode: CanvasNode,\n\tlookup?: ReadonlyMap<string, CanvasNode>,\n\toptions: ShapeNodePolygonOptions = {}\n): { rings: PolygonRing[]; fillRule: PolygonFillRule } | null {\n\tconst shape = getNodeShape(node);\n\tif (!shape) return null;\n\tconst size = getNodeSize(node, options);\n\tconst origin = node.parentId && lookup ? getAbsolutePosition(node, lookup) : node.position;\n\tconst { rings, fillRule } = shapeToPolygon(shape, size, { tolerance: options.tolerance });\n\treturn {\n\t\trings: rings.map((ring) => ring.map(([x, y]) => [x + origin.x, y + origin.y] as Point)),\n\t\tfillRule\n\t};\n}\n\n/**\n * A shape as an exact Bézier path in node-local pixels (rotation and path view boxes\n * applied; arcs become cubics), with the fill rule it is read with.\n */\nexport function shapeToBezierPath(\n\tshape: ShapeDescription,\n\tsize: ShapeSize\n): { path: BezierPath; fillRule: PolygonFillRule } {\n\tconst { width: w, height: h } = size;\n\tif (!(w > 0) || !(h > 0)) return { path: [], fillRule: 'nonzero' };\n\tconst svg = getShapeSvg({ ...shape, rotation: 0 }, size);\n\tlet path = toBezierPath(svg.d);\n\tif (shape.kind === 'path' && shape.viewBox) {\n\t\tconst vb = shape.viewBox;\n\t\tif (vb.length === 4 && vb.every(Number.isFinite) && vb[2] > 0 && vb[3] > 0) {\n\t\t\tconst sx = w / vb[2];\n\t\t\tconst sy = h / vb[3];\n\t\t\tpath = transformBezierPath(path, [sx, 0, 0, sy, -vb[0] * sx, -vb[1] * sy]);\n\t\t}\n\t}\n\tconst deg = shape.rotation ?? 0;\n\tif (deg && Number.isFinite(deg)) {\n\t\tconst rad = (deg * Math.PI) / 180;\n\t\tconst cos = Math.cos(rad);\n\t\tconst sin = Math.sin(rad);\n\t\tconst cx = w / 2;\n\t\tconst cy = h / 2;\n\t\tconst m: AffineMatrix = [cos, sin, -sin, cos, cx - cx * cos + cy * sin, cy - cx * sin - cy * cos];\n\t\tpath = transformBezierPath(path, m);\n\t}\n\treturn { path, fillRule: svg.fillRule };\n}\n\n/**\n * A shape node's outline as an exact Bézier path in absolute flow coordinates (parent\n * offsets, size and rotation applied), or `null` for non-shape nodes.\n */\nexport function shapeNodeToBezierPath(\n\tnode: CanvasNode,\n\tlookup?: ReadonlyMap<string, CanvasNode>,\n\toptions: NodeRectOptions = {}\n): { path: BezierPath; fillRule: PolygonFillRule } | null {\n\tconst shape = getNodeShape(node);\n\tif (!shape) return null;\n\tconst size = getNodeSize(node, options);\n\tconst origin = node.parentId && lookup ? getAbsolutePosition(node, lookup) : node.position;\n\tconst { path, fillRule } = shapeToBezierPath(shape, size);\n\treturn { path: transformBezierPath(path, [1, 0, 0, 1, origin.x, origin.y]), fillRule };\n}\n\n/** Why `combineShapeNodes` refused. */\nexport type CombineShapeNodesRefusal =\n\t/** A selected shape is locked; locked shapes are never consumed. */\n\t| 'locked'\n\t/** Fewer than two shape nodes were selected. */\n\t| 'too-few'\n\t/** The operation leaves nothing (e.g. intersecting shapes that do not overlap). */\n\t| 'empty';\n\nexport interface CombineShapeNodesOptions extends ShapeNodePolygonOptions {\n\t/**\n\t * Which shape's node style and data the result inherits: `bottom` (default, like\n\t * Freeform: the back-most shape), `top`, `first` (first id passed), or a node id.\n\t * Divide pieces always take the style of the top-most shape covering them.\n\t */\n\tstyleFrom?: 'bottom' | 'top' | 'first' | { id: string };\n\t/** Id factory for new nodes (default `crypto.randomUUID`, with a counter fallback). */\n\tcreateId?: (index: number) => string;\n\t/** Node `type` for the new nodes (default: the style source's type). */\n\tnodeType?: string;\n\t/** Decimal places in the path data (default 3). */\n\tprecision?: number;\n\t/** Ramer–Douglas–Peucker simplification tolerance for the result (default 0, off). */\n\tsimplify?: number;\n\t/**\n\t * Geometry engine. `'curve'` (default) computes the operation on the exact Bézier\n\t * outlines and keeps the inputs' curves (circles stay cubic arcs, rectangles stay four\n\t * lines); it falls back to `'polygon'` when it cannot resolve the input (reported as\n\t * `engine` / `fallbackReason` on the result). `'polygon'` flattens the shapes and uses\n\t * `polygonBoolean` (the behaviour before curve booleans). `curves: 'lines'` implies\n\t * `'polygon'` unless an engine is named.\n\t */\n\tengine?: 'curve' | 'polygon';\n\t/** Called with the reason when the curve engine falls back to polygons. */\n\tonFallback?: (reason: string) => void;\n\t/**\n\t * Polygon engine only: how the result's outline is written: `'fit'` (default) fits\n\t * smooth cubic curves to the flattened result (sharp corners and straight edges are\n\t * kept); `'lines'` writes the polygon as straight segments.\n\t */\n\tcurves?: 'lines' | 'fit';\n\t/** Curve-fitting tolerance in flow units for `curves: 'fit'` (default: `tolerance`, 0.25). */\n\tcurveTolerance?: number;\n\t/** Corner threshold in degrees for `curves: 'fit'` (default 30). */\n\tcornerAngleDeg?: number;\n\t/** Point-merge tolerance passed to the boolean engine. */\n\tepsilon?: number;\n\t/** Select the new nodes (default `true`). */\n\tselect?: boolean;\n}\n\nexport type CombineShapeNodesResult<TNode extends CanvasNode = CanvasNode> =\n\t| {\n\t\t\tok: true;\n\t\t\toperation: ShapeOperation;\n\t\t\t/** Ids of the shape nodes consumed by the operation. */\n\t\t\tremoveIds: string[];\n\t\t\t/** New path shape node(s): one, or one per region for `divide`. */\n\t\t\tnodes: TNode[];\n\t\t\t/** Engine that produced the geometry (`polygon` after a fallback). */\n\t\t\tengine: 'curve' | 'polygon';\n\t\t\t/** Why the curve engine fell back to polygons. */\n\t\t\tfallbackReason?: string;\n\t }\n\t| { ok: false; operation: ShapeOperation; reason: CombineShapeNodesRefusal };\n\nlet idCounter = 0;\nfunction defaultId(index: number): string {\n\tif (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') return crypto.randomUUID();\n\tidCounter += 1;\n\treturn `shape-${Date.now().toString(36)}-${idCounter}-${index}`;\n}\n\n/** Back-to-front order: `zIndex` (missing = 0), then array order, as xyflow stacks nodes. */\nfunction stackOrder<TNode extends CanvasNode>(nodes: TNode[], ids: Set<string>): TNode[] {\n\treturn nodes\n\t\t.map((node, index) => ({ node, index, z: typeof node.zIndex === 'number' ? node.zIndex : 0 }))\n\t\t.filter((entry) => ids.has(entry.node.id))\n\t\t.sort((a, b) => a.z - b.z || a.index - b.index)\n\t\t.map((entry) => entry.node);\n}\n\n/**\n * Checks whether `combineShapeNodes` can run, without computing geometry (it cannot\n * predict an `empty` result). Use it to enable menu items.\n */\nexport function canCombineShapeNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>\n): { ok: true; shapeIds: string[] } | { ok: false; reason: Exclude<CombineShapeNodesRefusal, 'empty'> } {\n\tconst idSet = new Set(ids);\n\tconst shapes = nodes.filter((n) => idSet.has(n.id) && isShapeNode(n));\n\tif (shapes.some((n) => isLocked(n))) return { ok: false, reason: 'locked' };\n\tif (shapes.length < 2) return { ok: false, reason: 'too-few' };\n\treturn { ok: true, shapeIds: shapes.map((n) => n.id) };\n}\n\n/**\n * Freeform-style shape operations on canvas nodes.\n *\n * - Only shape nodes (`data.shape`) among `ids` take part; other selected nodes are ignored.\n * - Refuses (`ok: false`) when a selected shape is locked, when fewer than two shapes are\n * selected, or when the result is empty. Nothing should be changed on refusal.\n * - `subtract` removes every other shape from the back-most one; the other operations are\n * symmetric. `divide` returns one node per distinct region of the overlay.\n * - Each new node is a `path` shape: its position / size is the result's bounding box and\n * its path data is node-local with `viewBox = [0, 0, width, height]`, so it scales with\n * the node. Rotation is baked into the path. Node fields and `data` (other than the\n * shape geometry, lock state and size) come from the style source.\n * - When all shapes share a parent, the new nodes keep it; otherwise they are top-level.\n *\n * Pure: returns the change; apply it with `applyShapeCombination` (or the arrange\n * controller's `combineShapes`, which also records one undo step).\n */\nexport function combineShapeNodes<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tids: Iterable<string>,\n\toperation: ShapeOperation,\n\toptions: CombineShapeNodesOptions = {}\n): CombineShapeNodesResult<TNode> {\n\tconst idList = [...ids];\n\tconst check = canCombineShapeNodes(nodes, idList);\n\tif (!check.ok) return { ok: false, operation, reason: check.reason };\n\n\tconst lookup = createNodeLookup(nodes);\n\tconst shapeIds = new Set(check.shapeIds);\n\tconst ordered = stackOrder(nodes, shapeIds);\n\n\tconst parentIds = new Set(ordered.map((n) => n.parentId ?? ''));\n\tconst sharedParent = parentIds.size === 1 ? ordered[0].parentId : undefined;\n\tconst parentOrigin =\n\t\tsharedParent && lookup.get(sharedParent)\n\t\t\t? getAbsolutePosition(lookup.get(sharedParent)!, lookup)\n\t\t\t: { x: 0, y: 0 };\n\n\tconst pickSource = (): TNode => {\n\t\tconst style = options.styleFrom ?? 'bottom';\n\t\tif (style === 'top') return ordered[ordered.length - 1];\n\t\tif (style === 'first') {\n\t\t\tconst first = idList.find((id) => shapeIds.has(id));\n\t\t\treturn (first && lookup.get(first)) || ordered[0];\n\t\t}\n\t\tif (typeof style === 'object') return (shapeIds.has(style.id) && lookup.get(style.id)) || ordered[0];\n\t\treturn ordered[0];\n\t};\n\n\tconst createId = options.createId ?? defaultId;\n\tconst buildOptions: BuildPathNodeOptions = {\n\t\tprecision: options.precision,\n\t\tnodeType: options.nodeType,\n\t\tselect: options.select,\n\t\tparentOrigin,\n\t\tsharedParent\n\t};\n\tconst engine = options.engine ?? (options.curves === 'lines' ? 'polygon' : 'curve');\n\n\tconst created: TNode[] = [];\n\tlet report: PathOpsReport = { engine: 'curve' };\n\tif (engine === 'curve') {\n\t\tconst operands = ordered.map((node) => shapeNodeToBezierPath(node, lookup, options)!);\n\t\tconst pathOptions = { epsilon: options.epsilon, onFallback: options.onFallback, fallbackTolerance: options.tolerance };\n\t\tif (operation === 'divide') {\n\t\t\tconst result = dividePaths(operands, pathOptions);\n\t\t\treport = result;\n\t\t\tfor (const piece of result.pieces) {\n\t\t\t\tconst source = ordered[piece.operands[piece.operands.length - 1]];\n\t\t\t\tconst node = buildPathNode(piece.path, source, createId(created.length), buildOptions);\n\t\t\t\tif (node) created.push(node);\n\t\t\t}\n\t\t} else {\n\t\t\tconst result = pathBoolean(BOOLEAN_FOR[operation], operands, pathOptions);\n\t\t\treport = result;\n\t\t\tconst node = buildPathNode(result.path, pickSource(), createId(0), buildOptions);\n\t\t\tif (node) created.push(node);\n\t\t}\n\t} else {\n\t\treport = { engine: 'polygon' };\n\t\tconst operands = ordered.map((node) => shapeNodeToPolygon(node, lookup, options)!);\n\t\tconst write = (polygon: MultiPolygon): BezierPath =>\n\t\t\ttoBezierPath(\n\t\t\t\tpolygonToSvgPath(polygon, {\n\t\t\t\t\tprecision: 9,\n\t\t\t\t\tsimplify: options.simplify,\n\t\t\t\t\tcurves: options.curves ?? 'fit',\n\t\t\t\t\tcurveTolerance: options.curveTolerance ?? options.tolerance ?? 0.25,\n\t\t\t\t\tcornerAngleDeg: options.cornerAngleDeg\n\t\t\t\t})\n\t\t\t);\n\t\tif (operation === 'divide') {\n\t\t\tfor (const piece of dividePolygons(operands, { epsilon: options.epsilon })) {\n\t\t\t\tconst source = ordered[piece.operands[piece.operands.length - 1]];\n\t\t\t\tconst node = buildPathNode(write([piece.polygon]), source, createId(created.length), buildOptions);\n\t\t\t\tif (node) created.push(node);\n\t\t\t}\n\t\t} else {\n\t\t\tconst result = polygonBoolean(BOOLEAN_FOR[operation], operands, { epsilon: options.epsilon });\n\t\t\tconst node = buildPathNode(write(result), pickSource(), createId(0), buildOptions);\n\t\t\tif (node) created.push(node);\n\t\t}\n\t}\n\tif (created.length === 0) return { ok: false, operation, reason: 'empty' };\n\tconst done: CombineShapeNodesResult<TNode> = {\n\t\tok: true,\n\t\toperation,\n\t\tremoveIds: ordered.map((n) => n.id),\n\t\tnodes: created,\n\t\tengine: report.engine\n\t};\n\tif (report.fallbackReason) done.fallbackReason = report.fallbackReason;\n\treturn done;\n}\n\ninterface BuildPathNodeOptions {\n\tprecision?: number;\n\tnodeType?: string;\n\tselect?: boolean;\n\tparentOrigin: { x: number; y: number };\n\tsharedParent?: string;\n\t/** Paint overrides; `null` removes the key (e.g. an outlined stroke has no stroke). */\n\tpaint?: Partial<Record<'fill' | 'stroke' | 'strokeWidth' | 'opacity', string | number | null>>;\n}\n\n/**\n * A new `path` shape node for an absolute path: positioned at the path's bounding box with\n * node-local path data and `viewBox = [0, 0, width, height]`; node fields, data and paint\n * come from `source`. `null` when the path is empty or flat.\n */\nfunction buildPathNode<TNode extends CanvasNode>(\n\tpath: BezierPath,\n\tsource: TNode,\n\tid: string,\n\toptions: BuildPathNodeOptions\n): TNode | null {\n\tconst bounds = bezierPathBounds(path);\n\tif (!bounds) return null;\n\tconst width = bounds.maxX - bounds.minX;\n\tconst height = bounds.maxY - bounds.minY;\n\tif (!(width > 0) || !(height > 0)) return null;\n\tconst d = bezierPathToSvg(path, { precision: options.precision ?? 3, offset: { x: -bounds.minX, y: -bounds.minY } });\n\tif (!d) return null;\n\tconst sourceShape = getNodeShape(source)!;\n\tconst shape: PathShape = {\n\t\tkind: 'path',\n\t\td,\n\t\tviewBox: [0, 0, roundSize(width), roundSize(height)],\n\t\tfillRule: 'nonzero'\n\t};\n\tfor (const key of ['fill', 'stroke', 'strokeWidth', 'opacity'] as const) {\n\t\tif (sourceShape[key] !== undefined) (shape as any)[key] = sourceShape[key];\n\t}\n\tfor (const [key, value] of Object.entries(options.paint ?? {})) {\n\t\tif (value === null) delete (shape as any)[key];\n\t\telse if (value !== undefined) (shape as any)[key] = value;\n\t}\n\tconst {\n\t\t[LOCKED_DATA_KEY]: _locked,\n\t\t[LOCKED_PREVIOUS_FLAGS_KEY]: _previous,\n\t\t...data\n\t} = source.data ?? {};\n\tconst {\n\t\tmeasured: _measured,\n\t\tselected: _selected,\n\t\tdragging: _dragging,\n\t\tpositionAbsolute: _positionAbsolute,\n\t\tinternals: _internals,\n\t\tparentId: _parentId,\n\t\t...rest\n\t} = source as CanvasNode;\n\tconst node: CanvasNode = {\n\t\t...rest,\n\t\tid,\n\t\tposition: { x: bounds.minX - options.parentOrigin.x, y: bounds.minY - options.parentOrigin.y },\n\t\twidth,\n\t\theight,\n\t\tdata: { ...data, [SHAPE_DATA_KEY]: shape }\n\t};\n\tif (options.sharedParent) node.parentId = options.sharedParent;\n\tif (options.nodeType !== undefined) node.type = options.nodeType;\n\tif (source.style) node.style = withStyleSize(source.style, { width, height });\n\tif (options.select !== false) node.selected = true;\n\treturn node as TNode;\n}\n\nfunction roundSize(value: number): number {\n\treturn Math.round(value * 1000) / 1000;\n}\n\n/**\n * Applies a successful `combineShapeNodes` result: removes the consumed nodes and inserts\n * the new ones at the array index of the first consumed node.\n * With `deselect` (default `true`) other nodes are deselected when the result is selected.\n */\nexport function applyShapeCombination<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tchange: { removeIds: Iterable<string>; nodes: TNode[] },\n\toptions: { deselect?: boolean } = {}\n): TNode[] {\n\tconst remove = new Set(change.removeIds);\n\tconst insertAt = nodes.findIndex((n) => remove.has(n.id));\n\tconst deselect = options.deselect !== false && change.nodes.some((n) => n.selected);\n\tconst out: TNode[] = [];\n\tnodes.forEach((node, index) => {\n\t\tif (index === insertAt) out.push(...change.nodes);\n\t\tif (remove.has(node.id)) return;\n\t\tout.push(deselect && node.selected ? ({ ...node, selected: false } as TNode) : node);\n\t});\n\tif (insertAt === -1) out.push(...change.nodes);\n\treturn out;\n}\n\n/**\n * Reverses `applyShapeCombination`: removes the created nodes and puts the consumed nodes\n * back at their original array indices (`previous` is the node array before the change).\n */\nexport function revertShapeCombination<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tchange: { removeIds: Iterable<string>; nodes: TNode[] },\n\tprevious: TNode[]\n): TNode[] {\n\tconst created = new Set(change.nodes.map((n) => n.id));\n\tconst removed = new Set(change.removeIds);\n\tconst out = nodes.filter((n) => !created.has(n.id));\n\tprevious.forEach((node, index) => {\n\t\tif (!removed.has(node.id) || out.some((n) => n.id === node.id)) return;\n\t\tout.splice(Math.min(index, out.length), 0, node);\n\t});\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// Offset Path and Outline Stroke on shape nodes\n// ---------------------------------------------------------------------------\n\n/** Per-node path edits: `offset` (Offset Path) and `outlineStroke` (Outline Stroke). */\nexport type ShapePathEditOperation = 'offset' | 'outlineStroke';\n\n/** Why `offsetShapeNodes` / `outlineShapeNodeStrokes` refused. */\nexport type ShapePathEditRefusal =\n\t/** A selected shape is locked. */\n\t| 'locked'\n\t/** No shape node was selected. */\n\t| 'no-shapes'\n\t/** Outline Stroke: no selected shape has a visible stroke. */\n\t| 'no-stroke'\n\t/** Every result is empty (e.g. an inset larger than the shapes). */\n\t| 'empty'\n\t/** The offset distance or stroke width is not a finite number. */\n\t| 'invalid';\n\nexport type ShapePathEditResult<TNode extends CanvasNode = CanvasNode> =\n\t| {\n\t\t\tok: true;\n\t\t\toperation: ShapePathEditOperation;\n\t\t\t/** Ids of the replaced nodes. */\n\t\t\tremoveIds: string[];\n\t\t\t/** All new nodes (a replaced node may come back with the same id). */\n\t\t\tnodes: TNode[];\n\t\t\t/** Each replaced node with the nodes that take its place, in stacking order. */\n\t\t\treplacements: { id: string; nodes: TNode[] }[];\n\t }\n\t| { ok: false; operation: ShapePathEditOperation; reason: ShapePathEditRefusal };\n\ninterface ShapePathEditBaseOptions extends NodeRectOptions {\n\t/** Id factory for new nodes (default `crypto.randomUUID`, with a counter fallback). */\n\tcreateId?: (index: number) => string;\n\t/** Node `type` for the new nodes (default: the source node's type). */\n\tnodeType?: string;\n\t/** Decimal places in the path data (default 3). */\n\tprecision?: number;\n\t/** Select the new nodes (default `true`). */\n\tselect?: boolean;\n\t/** Maximum error of the approximated offset curves in flow units (default 0.01). */\n\ttolerance?: number;\n\t/** Point-merge tolerance passed to the boolean engine. */\n\tepsilon?: number;\n\t/** Called with the reason when the curve engine falls back to polygons. */\n\tonFallback?: (reason: string) => void;\n}\n\nexport interface OffsetShapeNodesOptions extends ShapePathEditBaseOptions {\n\t/** Corner style of the offset outline (default `miter`). */\n\tjoin?: StrokeJoin;\n\t/** Miter limit (default 4). */\n\tmiterLimit?: number;\n\t/** Keep the original shape above the offset one (Illustrator style; default `false`: replace it). */\n\tkeepOriginal?: boolean;\n}\n\nexport interface OutlineShapeStrokesOptions extends ShapePathEditBaseOptions {\n\t/** Stroke width in flow units (default: the shape's `strokeWidth`, 1 when unset). */\n\twidth?: number;\n\t/** Default `miter`, as SVG. */\n\tjoin?: StrokeJoin;\n\t/** Default `butt`, as SVG. */\n\tcap?: StrokeCap;\n\t/** Default 4, as SVG. */\n\tmiterLimit?: number;\n\t/** Dash pattern (as `stroke-dasharray`). */\n\tdash?: readonly number[];\n\tdashOffset?: number;\n\t/**\n\t * Keep the shape's fill as the original node with its stroke removed, below the outlined\n\t * stroke (default `true`). With `false`, or when the shape has no visible fill, the node is\n\t * replaced by the outlined stroke alone.\n\t */\n\tkeepFill?: boolean;\n}\n\nconst INVISIBLE_PAINT = new Set(['none', 'transparent']);\n\n/** Whether a shape draws a stroke (a stroke colour or gradient other than `none` and a positive width). */\nexport function hasVisibleStroke(shape: ShapeDescription | null): boolean {\n\tif (!shape) return false;\n\tconst stroke = (shape as any).strokeGradient ?? shape.stroke;\n\tif (stroke === undefined || stroke === null) return false;\n\tif (typeof stroke === 'string' && INVISIBLE_PAINT.has(stroke.trim().toLowerCase())) return false;\n\tconst width = shape.strokeWidth ?? 1;\n\treturn Number.isFinite(width) && width > 0;\n}\n\nfunction hasVisibleFill(shape: ShapeDescription): boolean {\n\tif ((shape as any).fillGradient) return true;\n\tif (shape.fill === undefined) return true;\n\tif (typeof shape.fill === 'object' && shape.fill !== null) return true;\n\treturn !INVISIBLE_PAINT.has(String(shape.fill).trim().toLowerCase());\n}\n\nfunction shapeTargets(nodes: CanvasNode[], ids: Iterable<string>) {\n\tconst idSet = new Set(ids);\n\treturn nodes.filter((n) => idSet.has(n.id) && isShapeNode(n));\n}\n\n/** Checks whether `offsetShapeNodes` can run (it cannot predict an `empty` result). */\nexport function canOffsetShapeNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>\n): { ok: true; shapeIds: string[] } | { ok: false; reason: 'locked' | 'no-shapes' } {\n\tconst shapes = shapeTargets(nodes, ids);\n\tif (shapes.some((n) => isLocked(n))) return { ok: false, reason: 'locked' };\n\tif (shapes.length === 0) return { ok: false, reason: 'no-shapes' };\n\treturn { ok: true, shapeIds: shapes.map((n) => n.id) };\n}\n\n/** Checks whether `outlineShapeNodeStrokes` can run. */\nexport function canOutlineShapeNodeStrokes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>\n): { ok: true; shapeIds: string[] } | { ok: false; reason: 'locked' | 'no-shapes' | 'no-stroke' } {\n\tconst shapes = shapeTargets(nodes, ids);\n\tif (shapes.some((n) => isLocked(n))) return { ok: false, reason: 'locked' };\n\tif (shapes.length === 0) return { ok: false, reason: 'no-shapes' };\n\tconst stroked = shapes.filter((n) => hasVisibleStroke(getNodeShape(n)));\n\tif (stroked.length === 0) return { ok: false, reason: 'no-stroke' };\n\treturn { ok: true, shapeIds: stroked.map((n) => n.id) };\n}\n\nfunction editPlacement(node: CanvasNode, lookup: Map<string, CanvasNode>) {\n\tconst parent = node.parentId ? lookup.get(node.parentId) : undefined;\n\treturn {\n\t\tparentOrigin: parent ? getAbsolutePosition(parent, lookup) : { x: 0, y: 0 },\n\t\tsharedParent: parent ? node.parentId : undefined\n\t};\n}\n\nfunction finishEdit<TNode extends CanvasNode>(\n\toperation: ShapePathEditOperation,\n\treplacements: { id: string; nodes: TNode[] }[]\n): ShapePathEditResult<TNode> {\n\tif (replacements.length === 0) return { ok: false, operation, reason: 'empty' };\n\treturn {\n\t\tok: true,\n\t\toperation,\n\t\tremoveIds: replacements.map((r) => r.id),\n\t\tnodes: replacements.flatMap((r) => r.nodes),\n\t\treplacements\n\t};\n}\n\n/**\n * Offset Path on shape nodes: each selected shape grows (`distance > 0`) or shrinks\n * (`distance < 0`) by `distance` flow units, keeping curves. The result replaces the shape\n * (or sits below it with `keepOriginal`) as a `path` shape node with the same paint. Shapes\n * whose offset is empty are left unchanged; refuses when every result is empty, a selected\n * shape is locked or no shape is selected. Pure: apply with `applyShapeReplacement`.\n */\nexport function offsetShapeNodes<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tids: Iterable<string>,\n\tdistance: number,\n\toptions: OffsetShapeNodesOptions = {}\n): ShapePathEditResult<TNode> {\n\tconst operation = 'offset';\n\tif (!Number.isFinite(distance)) return { ok: false, operation, reason: 'invalid' };\n\tconst check = canOffsetShapeNodes(nodes, ids);\n\tif (!check.ok) return { ok: false, operation, reason: check.reason };\n\tconst lookup = createNodeLookup(nodes);\n\tconst createId = options.createId ?? defaultId;\n\tlet index = 0;\n\tconst replacements: { id: string; nodes: TNode[] }[] = [];\n\tfor (const id of check.shapeIds) {\n\t\tconst node = lookup.get(id) as TNode;\n\t\tconst outline = shapeNodeToBezierPath(node, lookup, options)!;\n\t\tconst path = offsetPath(outline.path, distance, {\n\t\t\tjoin: options.join,\n\t\t\tmiterLimit: options.miterLimit,\n\t\t\tfillRule: outline.fillRule,\n\t\t\ttolerance: options.tolerance,\n\t\t\tepsilon: options.epsilon,\n\t\t\tonFallback: options.onFallback\n\t\t});\n\t\tconst created = buildPathNode(path, node, createId(index), {\n\t\t\t...editPlacement(node, lookup),\n\t\t\tprecision: options.precision,\n\t\t\tnodeType: options.nodeType,\n\t\t\tselect: options.select\n\t\t});\n\t\tif (!created) continue;\n\t\tindex++;\n\t\tconst original = { ...node, selected: false } as TNode;\n\t\treplacements.push({ id, nodes: options.keepOriginal ? [created, original] : [created] });\n\t}\n\treturn finishEdit(operation, replacements);\n}\n\n/**\n * Outline Stroke on shape nodes: each selected shape with a visible stroke gets its stroke\n * converted to a filled `path` shape node (filled with the stroke colour, no stroke), using\n * the stroke width in flow units (strokes do not scale with the node) and SVG's default\n * miter join, miter limit 4 and butt caps unless overridden. The shape's fill stays as the\n * original node without its stroke, below the outline (`keepFill`). Pure: apply with\n * `applyShapeReplacement`.\n */\nexport function outlineShapeNodeStrokes<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tids: Iterable<string>,\n\toptions: OutlineShapeStrokesOptions = {}\n): ShapePathEditResult<TNode> {\n\tconst operation = 'outlineStroke';\n\tif (options.width !== undefined && !(Number.isFinite(options.width) && options.width > 0)) {\n\t\treturn { ok: false, operation, reason: 'invalid' };\n\t}\n\tconst check = canOutlineShapeNodeStrokes(nodes, ids);\n\tif (!check.ok) return { ok: false, operation, reason: check.reason };\n\tconst lookup = createNodeLookup(nodes);\n\tconst createId = options.createId ?? defaultId;\n\tlet index = 0;\n\tconst replacements: { id: string; nodes: TNode[] }[] = [];\n\tfor (const id of check.shapeIds) {\n\t\tconst node = lookup.get(id) as TNode;\n\t\tconst shape = getNodeShape(node)!;\n\t\tconst outline = shapeNodeToBezierPath(node, lookup, options)!;\n\t\tconst path = outlineStroke(outline.path, {\n\t\t\twidth: options.width ?? shape.strokeWidth ?? 1,\n\t\t\tjoin: options.join,\n\t\t\tcap: options.cap,\n\t\t\tmiterLimit: options.miterLimit,\n\t\t\tdash: options.dash,\n\t\t\tdashOffset: options.dashOffset,\n\t\t\ttolerance: options.tolerance,\n\t\t\tepsilon: options.epsilon,\n\t\t\tonFallback: options.onFallback\n\t\t});\n\t\tconst created = buildPathNode(path, node, createId(index), {\n\t\t\t...editPlacement(node, lookup),\n\t\t\tprecision: options.precision,\n\t\t\tnodeType: options.nodeType,\n\t\t\tselect: options.select,\n\t\t\tpaint: { fill: shape.stroke as string, stroke: null, strokeWidth: null }\n\t\t});\n\t\tif (!created) continue;\n\t\tindex++;\n\t\tconst replacement: TNode[] = [];\n\t\tif (options.keepFill !== false && hasVisibleFill(shape)) {\n\t\t\tconst { stroke: _stroke, strokeWidth: _width, ...rest } = shape;\n\t\t\treplacement.push({\n\t\t\t\t...node,\n\t\t\t\tselected: options.select === false ? node.selected : false,\n\t\t\t\tdata: { ...node.data, [SHAPE_DATA_KEY]: rest }\n\t\t\t} as TNode);\n\t\t}\n\t\treplacement.push(created);\n\t\treplacements.push({ id, nodes: replacement });\n\t}\n\treturn finishEdit(operation, replacements);\n}\n\n/**\n * Applies a successful `offsetShapeNodes` / `outlineShapeNodeStrokes` result: each replaced\n * node is swapped in place for its replacement nodes, so the stacking order of everything\n * else is kept. With `deselect` (default `true`) other nodes are deselected when the\n * result is selected. Undo with `revertShapeCombination(nodes, result, previous)`.\n */\nexport function applyShapeReplacement<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tchange: { replacements: { id: string; nodes: TNode[] }[] },\n\toptions: { deselect?: boolean } = {}\n): TNode[] {\n\tconst byId = new Map(change.replacements.map((r) => [r.id, r.nodes]));\n\tconst created = new Set(change.replacements.flatMap((r) => r.nodes.map((n) => n.id)));\n\tconst deselect = options.deselect !== false && change.replacements.some((r) => r.nodes.some((n) => n.selected));\n\tconst out: TNode[] = [];\n\tfor (const node of nodes) {\n\t\tconst replacement = byId.get(node.id);\n\t\tif (replacement) {\n\t\t\tout.push(...replacement);\n\t\t\tcontinue;\n\t\t}\n\t\tif (created.has(node.id)) continue;\n\t\tout.push(deselect && node.selected ? ({ ...node, selected: false } as TNode) : node);\n\t}\n\treturn out;\n}\n","/**\n * Resize snapping: the moving edges of a box being resized land on other shapes' edges and\n * centres (or on a grid) while the opposite edge / corner stays put.\n *\n * Portions ported from vectorcraft-main/crates/tools/src/guides.rs (`snap_scale`) and\n * vectorcraft-main/crates/geom/src/snap.rs (MIT OR Apache-2.0, © ArtCraft Team and\n * contributors). See THIRD_PARTY_NOTICES.md.\n */\nimport { boundsOf } from './layout.js';\nimport {\n\tcreateNodeLookup,\n\tgetAbsolutePosition,\n\tgetNodeSize,\n\tisLocked,\n\ttype CanvasNodeUpdate,\n\ttype NodeRectOptions\n} from './arrange.js';\nimport {\n\tcreateGuideIndex,\n\tcreateNodeGuideIndex,\n\tsnapToGrid,\n\ttype GuideIndex,\n\ttype GuideLine,\n\ttype SnapGuidesOptions\n} from './guides.js';\nimport type { CanvasNode, Rect } from './types.js';\n\n/** A resize handle, named like xyflow's `ControlPosition`. */\nexport type ResizeHandle =\n\t| 'top-left'\n\t| 'top'\n\t| 'top-right'\n\t| 'right'\n\t| 'bottom-right'\n\t| 'bottom'\n\t| 'bottom-left'\n\t| 'left';\n\nexport const RESIZE_HANDLES: readonly ResizeHandle[] = [\n\t'top-left',\n\t'top',\n\t'top-right',\n\t'right',\n\t'bottom-right',\n\t'bottom',\n\t'bottom-left',\n\t'left'\n];\n\n/** Which edge of each axis a handle moves: -1 the start (left / top), 1 the end, 0 none. */\nexport function resizeHandleAxes(handle: ResizeHandle): { x: -1 | 0 | 1; y: -1 | 0 | 1 } {\n\tconst x = handle.includes('left') ? -1 : handle.includes('right') ? 1 : 0;\n\tconst y = handle.includes('top') ? -1 : handle.includes('bottom') ? 1 : 0;\n\treturn { x, y };\n}\n\n/**\n * The handle a resize from `start` to `next` was made with, from the edges that moved, or\n * `null` when no edge moved.\n */\nexport function inferResizeHandle(start: Rect, next: Rect, eps = 1e-6): ResizeHandle | null {\n\tconst moved = (a: number, b: number) => Math.abs(a - b) > eps;\n\tconst left = moved(start.x, next.x);\n\tconst right = moved(start.x + start.width, next.x + next.width);\n\tconst top = moved(start.y, next.y);\n\tconst bottom = moved(start.y + start.height, next.y + next.height);\n\tconst h = left ? 'left' : right ? 'right' : '';\n\tconst v = top ? 'top' : bottom ? 'bottom' : '';\n\tif (h && v) return `${v}-${h}` as ResizeHandle;\n\treturn (h || v || null) as ResizeHandle | null;\n}\n\nexport interface SnapResizeOptions extends Omit<SnapGuidesOptions, 'spacing'> {\n\t/**\n\t * Keep the input box's aspect ratio. A corner snaps whichever axis is closer to a target\n\t * and the other follows; a side scales the other axis about its centre.\n\t */\n\tkeepAspectRatio?: boolean;\n\t/** Smallest width a snap may leave (default: anything above 0). */\n\tminWidth?: number;\n\t/** Smallest height a snap may leave (default: anything above 0). */\n\tminHeight?: number;\n}\n\nexport interface SnapResizeResult {\n\t/** The snapped box. */\n\trect: Rect;\n\t/** Guide segments for the shape snaps that were applied (grid snaps draw none). */\n\tguides: GuideLine[];\n\t/** Whether the moving x edge snapped to a shape. */\n\tsnappedX: boolean;\n\t/** Whether the moving y edge snapped to a shape. */\n\tsnappedY: boolean;\n\t/** Whether `rect` differs from the input. */\n\tchanged: boolean;\n}\n\ninterface Hit {\n\tvalue: number;\n\tkind: 'edge' | 'centre' | 'grid';\n}\n\ntype Axis = GuideIndex['edgeX'];\n\nfunction within(axis: Axis, v: number, tolerance: number): number[] {\n\tconst { values } = axis;\n\tlet lo = 0;\n\tlet hi = values.length;\n\twhile (lo < hi) {\n\t\tconst mid = (lo + hi) >>> 1;\n\t\tif (values[mid] < v - tolerance) lo = mid + 1;\n\t\telse hi = mid;\n\t}\n\tconst out: number[] = [];\n\tfor (let i = lo; i < values.length && values[i] <= v + tolerance; i++) out.push(values[i]);\n\treturn out;\n}\n\nfunction matching(axis: Axis, v: number, eps: number): Rect[] {\n\tconst { values, rects } = axis;\n\tconst out: Rect[] = [];\n\tfor (let i = 0; i < values.length; i++) if (Math.abs(values[i] - v) <= eps) out.push(rects[i]);\n\treturn out;\n}\n\n/**\n * Snaps a resize of the axis-aligned box `rect` made with `handle` against `targets`. The\n * opposite edge (side handles) or corner (corner handles) stays fixed:\n *\n * - A **side** handle snaps its one moving edge to the nearest target edge or centre within\n * `threshold` screen pixels.\n * - A **corner** handle snaps x and y independently (unless `keepAspectRatio`).\n * - A snap that would collapse or flip the box (or go below `minWidth` / `minHeight`) is\n * rejected; the next nearest target is tried, else the edge stays where it was dragged.\n * - With `gridSize`, an axis without a shape snap snaps its moving edge to the grid.\n *\n * Returns the snapped box and the guides to draw (same shape as drag snapping).\n */\nexport function snapResize(\n\trect: Rect,\n\thandle: ResizeHandle,\n\ttargets: Rect[] | GuideIndex,\n\toptions: SnapResizeOptions = {}\n): SnapResizeResult {\n\tconst index = Array.isArray(targets) ? createGuideIndex(targets) : targets;\n\tconst zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;\n\tconst tolerance = (options.threshold ?? 6) / zoom;\n\tconst eps = 0.5 / zoom;\n\tconst useEdges = options.edges !== false;\n\tconst useCentres = options.centres !== false;\n\tconst grid = options.gridSize ?? 0;\n\tconst axes = resizeHandleAxes(handle);\n\tconst unchanged: SnapResizeResult = { rect: { ...rect }, guides: [], snappedX: false, snappedY: false, changed: false };\n\tif (![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite)) return unchanged;\n\n\tconst x0 = rect.x;\n\tconst x1 = rect.x + rect.width;\n\tconst y0 = rect.y;\n\tconst y1 = rect.y + rect.height;\n\n\t/** Best valid target for one moving edge, or `null`. */\n\tconst bestHit = (horizontal: boolean): Hit | null => {\n\t\tconst dir = horizontal ? axes.x : axes.y;\n\t\tif (dir === 0) return null;\n\t\tconst moving = horizontal ? (dir < 0 ? x0 : x1) : dir < 0 ? y0 : y1;\n\t\tconst fixed = horizontal ? (dir < 0 ? x1 : x0) : dir < 0 ? y1 : y0;\n\t\tconst min = (horizontal ? options.minWidth : options.minHeight) ?? 0;\n\t\tconst valid = (t: number) => {\n\t\t\tconst size = dir < 0 ? fixed - t : t - fixed;\n\t\t\treturn Number.isFinite(size) && size > 1e-9 && size >= min;\n\t\t};\n\t\tlet best: Hit | null = null;\n\t\tconst consider = (values: number[], kind: Hit['kind']) => {\n\t\t\tfor (const t of values) {\n\t\t\t\tif (!valid(t)) continue;\n\t\t\t\tif (!best || Math.abs(t - moving) < Math.abs(best.value - moving)) best = { value: t, kind };\n\t\t\t}\n\t\t};\n\t\tif (useEdges) consider(within(horizontal ? index.edgeX : index.edgeY, moving, tolerance), 'edge');\n\t\tif (useCentres) consider(within(horizontal ? index.centreX : index.centreY, moving, tolerance), 'centre');\n\t\tif (!best && grid > 0) {\n\t\t\tconst origin = horizontal ? options.gridOrigin?.x ?? 0 : options.gridOrigin?.y ?? 0;\n\t\t\tconsider([snapToGrid(moving, grid, origin)], 'grid');\n\t\t}\n\t\treturn best;\n\t};\n\n\tlet nx0 = x0;\n\tlet nx1 = x1;\n\tlet ny0 = y0;\n\tlet ny1 = y1;\n\tconst setEdge = (horizontal: boolean, value: number) => {\n\t\tconst dir = horizontal ? axes.x : axes.y;\n\t\tif (horizontal) {\n\t\t\tif (dir < 0) nx0 = value;\n\t\t\telse nx1 = value;\n\t\t} else if (dir < 0) ny0 = value;\n\t\telse ny1 = value;\n\t};\n\n\tlet hitX = bestHit(true);\n\tlet hitY = bestHit(false);\n\tconst ratio = rect.height > 0 ? rect.width / rect.height : 0;\n\n\tif (options.keepAspectRatio && ratio > 0 && (hitX || hitY)) {\n\t\tif (axes.x !== 0 && axes.y !== 0) {\n\t\t\t// Corner: the closer snap wins; the other axis follows from the fixed corner.\n\t\t\tconst dist = (h: Hit | null, horizontal: boolean) =>\n\t\t\t\th ? Math.abs(h.value - (horizontal ? (axes.x < 0 ? x0 : x1) : axes.y < 0 ? y0 : y1)) : Infinity;\n\t\t\tconst useX = dist(hitX, true) <= dist(hitY, false);\n\t\t\tif (useX && hitX) {\n\t\t\t\tsetEdge(true, hitX.value);\n\t\t\t\tconst height = (nx1 - nx0) / ratio;\n\t\t\t\tif (axes.y < 0) ny0 = ny1 - height;\n\t\t\t\telse ny1 = ny0 + height;\n\t\t\t\thitY = null;\n\t\t\t} else if (hitY) {\n\t\t\t\tsetEdge(false, hitY.value);\n\t\t\t\tconst width = (ny1 - ny0) * ratio;\n\t\t\t\tif (axes.x < 0) nx0 = nx1 - width;\n\t\t\t\telse nx1 = nx0 + width;\n\t\t\t\thitX = null;\n\t\t\t}\n\t\t\tconst min = (v: number | undefined) => v ?? 0;\n\t\t\tif (nx1 - nx0 < min(options.minWidth) || ny1 - ny0 < min(options.minHeight) || !(nx1 > nx0 && ny1 > ny0)) {\n\t\t\t\treturn unchanged;\n\t\t\t}\n\t\t} else if (axes.x !== 0 && hitX) {\n\t\t\tsetEdge(true, hitX.value);\n\t\t\tconst height = (nx1 - nx0) / ratio;\n\t\t\tconst cy = (y0 + y1) / 2;\n\t\t\tny0 = cy - height / 2;\n\t\t\tny1 = cy + height / 2;\n\t\t} else if (axes.y !== 0 && hitY) {\n\t\t\tsetEdge(false, hitY.value);\n\t\t\tconst width = (ny1 - ny0) * ratio;\n\t\t\tconst cx = (x0 + x1) / 2;\n\t\t\tnx0 = cx - width / 2;\n\t\t\tnx1 = cx + width / 2;\n\t\t}\n\t} else {\n\t\tif (hitX) setEdge(true, hitX.value);\n\t\tif (hitY) setEdge(false, hitY.value);\n\t}\n\n\tconst out: Rect = { x: nx0, y: ny0, width: nx1 - nx0, height: ny1 - ny0 };\n\tconst guides: GuideLine[] = [];\n\tconst guideFor = (horizontal: boolean, hit: Hit | null) => {\n\t\tif (!hit || hit.kind === 'grid') return;\n\t\tconst axis =\n\t\t\thit.kind === 'edge' ? (horizontal ? index.edgeX : index.edgeY) : horizontal ? index.centreX : index.centreY;\n\t\tconst aligned = matching(axis, hit.value, eps);\n\t\tconst span = boundsOf([out, ...aligned])!;\n\t\tguides.push(\n\t\t\thorizontal\n\t\t\t\t? { orientation: 'vertical', kind: hit.kind, x1: hit.value, y1: span.y, x2: hit.value, y2: span.y + span.height }\n\t\t\t\t: { orientation: 'horizontal', kind: hit.kind, x1: span.x, y1: hit.value, x2: span.x + span.width, y2: hit.value }\n\t\t);\n\t};\n\tguideFor(true, hitX);\n\tguideFor(false, hitY);\n\tconst changed = out.x !== rect.x || out.y !== rect.y || out.width !== rect.width || out.height !== rect.height;\n\treturn {\n\t\trect: out,\n\t\tguides,\n\t\tsnappedX: !!hitX && hitX.kind !== 'grid',\n\t\tsnappedY: !!hitY && hitY.kind !== 'grid',\n\t\tchanged\n\t};\n}\n\nexport interface SnapNodeResizeOptions extends SnapResizeOptions, NodeRectOptions {\n\t/** Index from `createNodeGuideIndex(nodes, [id])`, reused across resize frames. */\n\tindex?: GuideIndex;\n}\n\nexport interface SnapNodeResizeResult extends SnapResizeResult {\n\t/** Position / size update for the node (parent-relative position), or none. */\n\tupdates: CanvasNodeUpdate[];\n}\n\n/**\n * Snaps a node that is being resized. `rect` is the node's proposed absolute box (e.g. from\n * a resize event) and `handle` the handle being dragged. Locked nodes are never snapped.\n * The node itself and its descendants are not snap targets.\n */\nexport function snapNodeResize(\n\tnodes: CanvasNode[],\n\tid: string,\n\trect: Rect,\n\thandle: ResizeHandle,\n\toptions: SnapNodeResizeOptions = {}\n): SnapNodeResizeResult {\n\tconst lookup = createNodeLookup(nodes);\n\tconst node = lookup.get(id);\n\tif (!node || isLocked(node)) {\n\t\treturn { rect: { ...rect }, guides: [], snappedX: false, snappedY: false, changed: false, updates: [] };\n\t}\n\tconst index = options.index ?? createNodeGuideIndex(nodes, [id], options);\n\tconst result = snapResize(rect, handle, index, options);\n\tif (!result.changed) return { ...result, updates: [] };\n\tconst parent = node.parentId ? lookup.get(node.parentId) : undefined;\n\tconst origin = parent ? getAbsolutePosition(parent, lookup) : { x: 0, y: 0 };\n\treturn {\n\t\t...result,\n\t\tupdates: [\n\t\t\t{\n\t\t\t\tid,\n\t\t\t\tposition: { x: result.rect.x - origin.x, y: result.rect.y - origin.y },\n\t\t\t\twidth: result.rect.width,\n\t\t\t\theight: result.rect.height\n\t\t\t}\n\t\t]\n\t};\n}\n\n/**\n * Absolute box of a node while it is being resized: flow libraries write the new size to\n * `width` / `height` before the DOM is re-measured, so those win over `measured` here.\n */\nexport function getResizingNodeRect(\n\tnode: CanvasNode,\n\tlookup: ReadonlyMap<string, CanvasNode>,\n\toptions: NodeRectOptions = {}\n): Rect {\n\tconst { x, y } = node.parentId ? getAbsolutePosition(node, lookup) : node.position;\n\tconst size = getNodeSize(node, options);\n\treturn { x, y, width: node.width ?? size.width, height: node.height ?? size.height };\n}\n","// Portions ported from figcraft/crates/layout/src/lib.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport { createNodeLookup, getAbsolutePosition, getNodeSize, type CanvasNodeUpdate, type NodeRectOptions } from './arrange.js';\nimport type { GuideLine } from './guides.js';\nimport type { CanvasNode, XYPosition } from './types.js';\n\n/**\n * Figma-style auto layout for xyflow sub-flows.\n *\n * A *frame* is any node with `data.layout` (an {@link AutoLayout}); its children are the nodes\n * whose `parentId` is the frame, in node-array order. Children may carry `data.childLayout`\n * (an {@link AutoLayoutChild}) for their own sizing, min / max, absolute positioning,\n * constraints and grid cell. The functions here are pure: they return `CanvasNodeUpdate`s\n * (positions relative to the parent, sizes) for `applyNodeUpdates`.\n */\n\n/** Data key holding a frame's {@link AutoLayout}. */\nexport const AUTO_LAYOUT_DATA_KEY = 'layout';\n/** Data key holding a child's {@link AutoLayoutChild}. */\nexport const CHILD_LAYOUT_DATA_KEY = 'childLayout';\n\nexport type AutoLayoutMode = 'horizontal' | 'vertical' | 'grid';\n/** `fixed` keeps the size, `hug` fits the content (frames and text), `fill` shares the parent's free space. */\nexport type AutoLayoutSizing = 'fixed' | 'hug' | 'fill';\nexport type AutoLayoutAlignMain = 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly';\nexport type AutoLayoutAlignCross = 'start' | 'center' | 'end' | 'stretch' | 'baseline';\n/** How wrapped lines share the free cross space: packed by `alignCross` (`auto`) or spread (`space-between`). */\nexport type AutoLayoutAlignContent = 'auto' | 'space-between';\n/** `number` (all sides), `[vertical, horizontal]`, `[top, right, bottom, left]` or per side. */\nexport type AutoLayoutPadding =\n\t| number\n\t| [number, number]\n\t| [number, number, number, number]\n\t| { top?: number; right?: number; bottom?: number; left?: number };\n\nexport interface AutoLayoutSize {\n\twidth?: number;\n\theight?: number;\n}\n\nexport interface AutoLayoutSizingPair {\n\twidth?: AutoLayoutSizing;\n\theight?: AutoLayoutSizing;\n}\n\n/** A grid track: pixels, `'hug'` (fits its content), `'2fr'` or `{ fr: 2 }` (shares the rest like CSS `fr`). */\nexport type AutoLayoutGridTrack = number | 'hug' | `${number}fr` | { fr: number };\n\nexport interface AutoLayoutGrid {\n\t/** Column count (all `1fr`) or the column tracks (default 2 columns). */\n\tcolumns?: number | AutoLayoutGridTrack[];\n\t/** Minimum row count or the row tracks (default 1); rows are added to fit the children. Missing tracks are `1fr`. */\n\trows?: number | AutoLayoutGridTrack[];\n}\n\n/** A frame's auto layout (`node.data.layout`). */\nexport interface AutoLayout {\n\tmode: AutoLayoutMode;\n\t/** Main-axis gap (grid: column gap). Negative gaps overlap. Default 0. */\n\tgap?: number;\n\t/** Gap between wrapped lines (grid: row gap). Default: `gap`. */\n\trowGap?: number;\n\t/** Default 0. */\n\tpadding?: AutoLayoutPadding;\n\t/** Horizontal / vertical: wrap into lines when the main axis runs out (ignored while hugging it). */\n\twrap?: boolean;\n\t/** Default `start`. Grid: the cell alignment along x (`start`, `center`, `end`). */\n\talignMain?: AutoLayoutAlignMain;\n\t/** Default `start`. `baseline` applies to horizontal layouts. Grid: the cell alignment along y. */\n\talignCross?: AutoLayoutAlignCross;\n\talignContent?: AutoLayoutAlignContent;\n\t/** The frame's own sizing (`childLayout.sizing` wins when both are set). Default `fixed`. */\n\tsizing?: AutoLayoutSizingPair;\n\t/** The frame's own limits (`childLayout.min` / `max` win when set). */\n\tmin?: AutoLayoutSize;\n\tmax?: AutoLayoutSize;\n\tgrid?: AutoLayoutGrid;\n}\n\nexport type AutoLayoutHorizontalConstraint = 'left' | 'right' | 'leftRight' | 'center' | 'scale';\nexport type AutoLayoutVerticalConstraint = 'top' | 'bottom' | 'topBottom' | 'center' | 'scale';\n\nexport interface AutoLayoutConstraints {\n\thorizontal?: AutoLayoutHorizontalConstraint;\n\tvertical?: AutoLayoutVerticalConstraint;\n}\n\nexport type AutoLayoutCellAlign = 'start' | 'center' | 'end';\n\n/** A grid child's cell. Column / row are 0-based anchors; unanchored children flow in reading order. */\nexport interface AutoLayoutGridArea {\n\tcolumn?: number;\n\trow?: number;\n\tcolumnSpan?: number;\n\trowSpan?: number;\n\t/** The child's own alignment in its cell (wins over the frame's). */\n\talignX?: AutoLayoutCellAlign;\n\talignY?: AutoLayoutCellAlign;\n}\n\n/** A child's layout properties (`node.data.childLayout`). */\nexport interface AutoLayoutChild {\n\tsizing?: AutoLayoutSizingPair;\n\tmin?: AutoLayoutSize;\n\tmax?: AutoLayoutSize;\n\t/** Ignore auto layout: keep its place and follow `constraints` when the frame resizes. */\n\tabsolute?: boolean;\n\t/** How the child follows its parent's resizes (default left / top). */\n\tconstraints?: AutoLayoutConstraints;\n\tgridArea?: AutoLayoutGridArea;\n}\n\n/**\n * Measuring hooks. `size` defaults to `getNodeSize` (xyflow `measured` size, then `width` /\n * `height`, then style). Nodes for which `textSize` returns a size are *text-like*: they can\n * hug, and wrap (`textHeight`) when their width is set by the layout.\n */\nexport interface AutoLayoutMeasure {\n\tsize?: (node: CanvasNode) => { width: number; height: number } | undefined;\n\t/** Natural (unwrapped) size of a text-like node. */\n\ttextSize?: (node: CanvasNode) => { width: number; height: number } | undefined;\n\t/** Height of a text-like node wrapped at `width`. */\n\ttextHeight?: (node: CanvasNode, width: number) => number | undefined;\n\t/** Distance from the node's top to its first text baseline (for `alignCross: 'baseline'`). */\n\ttextBaseline?: (node: CanvasNode) => number | undefined;\n}\n\nexport interface AutoLayoutOptions {\n\tmeasure?: AutoLayoutMeasure;\n\t/** Size resolution for unmeasured nodes when `measure.size` is not given. */\n\trectOptions?: NodeRectOptions;\n}\n\ntype Axis = 'width' | 'height';\n\nconst MODES = new Set(['horizontal', 'vertical', 'grid']);\nconst EPS = 1e-6;\nconst MAX_TRACKS = 1000;\nconst MAX_GRID_CELLS = 1_000_000;\n\n/** Clamps a size to `[min, max]`; non-finite or negative sizes become 0 and min wins over a smaller max. */\nexport function clampLayoutSize(value: number, min?: number, max?: number): number {\n\tlet v = Number.isFinite(value) ? Math.max(value, 0) : 0;\n\tif (typeof max === 'number' && Number.isFinite(max)) v = Math.min(v, max);\n\tif (typeof min === 'number' && Number.isFinite(min)) v = Math.max(v, min);\n\treturn v;\n}\n\nconst finite = (v: unknown): number => (typeof v === 'number' && Number.isFinite(v) ? v : 0);\n\n/** The node's auto layout, or `undefined` when it is not an auto layout frame. */\nexport function getAutoLayout(node: CanvasNode | undefined | null): AutoLayout | undefined {\n\tconst layout = node?.data?.[AUTO_LAYOUT_DATA_KEY];\n\treturn layout && typeof layout === 'object' && MODES.has(layout.mode) ? (layout as AutoLayout) : undefined;\n}\n\n/** Whether the node is an auto layout frame. */\nexport function isAutoLayoutFrame(node: CanvasNode | undefined | null): boolean {\n\treturn getAutoLayout(node) !== undefined;\n}\n\n/** The node's child layout properties (empty when unset). */\nexport function getChildLayout(node: CanvasNode | undefined | null): AutoLayoutChild {\n\tconst child = node?.data?.[CHILD_LAYOUT_DATA_KEY];\n\treturn child && typeof child === 'object' ? (child as AutoLayoutChild) : {};\n}\n\n/** Effective sizing of a node on one axis: `childLayout.sizing`, then `layout.sizing`, then `fixed`. */\nexport function getLayoutSizing(node: CanvasNode, axis: Axis): AutoLayoutSizing {\n\tconst value = getChildLayout(node).sizing?.[axis] ?? getAutoLayout(node)?.sizing?.[axis];\n\treturn value === 'hug' || value === 'fill' ? value : 'fixed';\n}\n\nfunction limitsOf(node: CanvasNode) {\n\tconst child = getChildLayout(node);\n\tconst layout = getAutoLayout(node);\n\tconst min = child.min ?? layout?.min ?? {};\n\tconst max = child.max ?? layout?.max ?? {};\n\treturn { minW: min.width, maxW: max.width, minH: min.height, maxH: max.height };\n}\n\n/** Padding as `[top, right, bottom, left]`, sanitised (non-finite and negative values become 0). */\nexport function resolvePadding(padding: AutoLayoutPadding | undefined): [number, number, number, number] {\n\tlet sides: [unknown, unknown, unknown, unknown];\n\tif (typeof padding === 'number') sides = [padding, padding, padding, padding];\n\telse if (Array.isArray(padding)) {\n\t\tsides = padding.length === 2 ? [padding[0], padding[1], padding[0], padding[1]] : [padding[0], padding[1], padding[2], padding[3]];\n\t} else if (padding && typeof padding === 'object') sides = [padding.top, padding.right, padding.bottom, padding.left];\n\telse sides = [0, 0, 0, 0];\n\treturn sides.map((p) => Math.max(finite(p), 0)) as [number, number, number, number];\n}\n\ninterface Box {\n\tid: string;\n\tnode: CanvasNode;\n\tparent?: string;\n\tx: number;\n\ty: number;\n\tw: number;\n\th: number;\n\tkids: string[];\n}\n\n/** Working copy of the node geometry the layout passes mutate. */\nclass LayoutState {\n\treadonly boxes = new Map<string, Box>();\n\treadonly measure: AutoLayoutMeasure;\n\tprivate readonly visiting = new Set<string>();\n\tprivate readonly original = new Map<string, { x: number; y: number; w: number; h: number }>();\n\n\tconstructor(nodes: CanvasNode[], options: AutoLayoutOptions = {}) {\n\t\tthis.measure = options.measure ?? {};\n\t\tfor (const node of nodes) {\n\t\t\tconst size = this.measure.size?.(node) ?? getNodeSize(node, options.rectOptions);\n\t\t\tconst box: Box = {\n\t\t\t\tid: node.id,\n\t\t\t\tnode,\n\t\t\t\tparent: node.parentId,\n\t\t\t\tx: finite(node.position?.x),\n\t\t\t\ty: finite(node.position?.y),\n\t\t\t\tw: finite(size.width),\n\t\t\t\th: finite(size.height),\n\t\t\t\tkids: []\n\t\t\t};\n\t\t\tthis.boxes.set(node.id, box);\n\t\t\tthis.original.set(node.id, { x: box.x, y: box.y, w: box.w, h: box.h });\n\t\t}\n\t\tfor (const node of nodes) {\n\t\t\tif (node.parentId && node.parentId !== node.id) this.boxes.get(node.parentId)?.kids.push(node.id);\n\t\t}\n\t}\n\n\tupdates(): CanvasNodeUpdate[] {\n\t\tconst out: CanvasNodeUpdate[] = [];\n\t\tfor (const [id, box] of this.boxes) {\n\t\t\tconst o = this.original.get(id)!;\n\t\t\tconst update: CanvasNodeUpdate = { id };\n\t\t\tif (Math.abs(box.x - o.x) > EPS || Math.abs(box.y - o.y) > EPS) update.position = { x: box.x, y: box.y };\n\t\t\tif (Math.abs(box.w - o.w) > EPS) update.width = box.w;\n\t\t\tif (Math.abs(box.h - o.h) > EPS) update.height = box.h;\n\t\t\tif (update.position || update.width !== undefined || update.height !== undefined) out.push(update);\n\t\t}\n\t\treturn out;\n\t}\n\n\t/** Runs `fn` for `id` unless it is already on the stack (guards `parentId` cycles). */\n\tguard(id: string, fn: () => void) {\n\t\tif (this.visiting.has(id)) return;\n\t\tthis.visiting.add(id);\n\t\ttry {\n\t\t\tfn();\n\t\t} finally {\n\t\t\tthis.visiting.delete(id);\n\t\t}\n\t}\n\n\tancestors(id: string): string[] {\n\t\tconst out: string[] = [];\n\t\tconst seen = new Set([id]);\n\t\tlet parent = this.boxes.get(id)?.parent;\n\t\twhile (parent && !seen.has(parent) && this.boxes.has(parent)) {\n\t\t\tout.push(parent);\n\t\t\tseen.add(parent);\n\t\t\tparent = this.boxes.get(parent)!.parent;\n\t\t}\n\t\treturn out;\n\t}\n}\n\nfunction setSize(s: LayoutState, id: string, w: number, h: number) {\n\tconst b = s.boxes.get(id);\n\tif (!b) return;\n\tb.w = Number.isFinite(w) ? Math.max(w, 0) : b.w;\n\tb.h = Number.isFinite(h) ? Math.max(h, 0) : b.h;\n}\n\nfunction place(s: LayoutState, id: string, x: number, y: number) {\n\tconst b = s.boxes.get(id);\n\tif (!b) return;\n\tb.x = x;\n\tb.y = y;\n}\n\nconst isText = (s: LayoutState, b: Box) => !!s.measure.textSize && s.measure.textSize(b.node) !== undefined;\n\n/** The children an auto layout frame places: visible and not absolute. */\nfunction flowChildren(s: LayoutState, frame: string): string[] {\n\tconst f = s.boxes.get(frame);\n\tif (!f) return [];\n\treturn f.kids.filter((k) => {\n\t\tconst n = s.boxes.get(k)!.node;\n\t\treturn n.hidden !== true && !getChildLayout(n).absolute;\n\t});\n}\n\n/** Text that hugs its width refreshes its natural size (wrapping at its max width); a hug-height text gets its wrapped height. */\nfunction fitHugText(s: LayoutState, id: string) {\n\tconst b = s.boxes.get(id);\n\tif (!b || !isText(s, b)) return;\n\tconst natural = s.measure.textSize!(b.node)!;\n\tconst { minW, maxW } = limitsOf(b.node);\n\tif (getLayoutSizing(b.node, 'width') === 'hug') {\n\t\tconst w = clampLayoutSize(natural.width, minW, maxW);\n\t\tconst limited = Math.abs(w - natural.width) > EPS;\n\t\tconst h = limited ? s.measure.textHeight?.(b.node, w) ?? natural.height : natural.height;\n\t\tsetSize(s, id, w, h);\n\t} else if (getLayoutSizing(b.node, 'height') === 'hug') {\n\t\tsetSize(s, id, b.w, s.measure.textHeight?.(b.node, b.w) ?? b.h);\n\t}\n}\n\n/** Clamps an in-flow child's own size to its min / max. */\nfunction enforceLimits(s: LayoutState, id: string) {\n\tconst b = s.boxes.get(id);\n\tif (!b) return;\n\tconst { minW, maxW, minH, maxH } = limitsOf(b.node);\n\tif (minW === undefined && maxW === undefined && minH === undefined && maxH === undefined) return;\n\tconst nw = clampLayoutSize(b.w, minW, maxW);\n\tconst nh = clampLayoutSize(b.h, minH, maxH);\n\tif (Math.abs(nw - b.w) < 1e-9 && Math.abs(nh - b.h) < 1e-9) return;\n\tif (isText(s, b)) setSize(s, id, nw, nh);\n\telse resizeChild(s, id, nw, nh);\n}\n\nfunction itemBaseline(s: LayoutState, id: string, depth: number): number | undefined {\n\tconst b = s.boxes.get(id);\n\tif (!b) return undefined;\n\tif (isText(s, b)) {\n\t\tconst v = s.measure.textBaseline?.(b.node);\n\t\treturn typeof v === 'number' && Number.isFinite(v) ? v : undefined;\n\t}\n\tconst layout = getAutoLayout(b.node);\n\tif (!layout || layout.mode === 'grid' || depth >= 32) return undefined;\n\tfor (const k of flowChildren(s, id)) {\n\t\tconst base = itemBaseline(s, k, depth + 1);\n\t\tif (base !== undefined) return s.boxes.get(k)!.y + base;\n\t}\n\treturn undefined;\n}\n\n/** Resizes a child; text with hugging height re-wraps, frames apply constraints and re-run their layout. */\nfunction resizeChild(s: LayoutState, id: string, w: number, h: number) {\n\tconst b = s.boxes.get(id);\n\tif (!b) return;\n\tif (isText(s, b) && getLayoutSizing(b.node, 'height') === 'hug') {\n\t\tsetSize(s, id, w, s.measure.textHeight?.(b.node, w) ?? h);\n\t\treturn;\n\t}\n\tconst ow = b.w;\n\tconst oh = b.h;\n\tsetSize(s, id, w, h);\n\tif (b.kids.length > 0) constrain(s, id, ow, oh);\n\tif (getAutoLayout(b.node)) layout(s, id);\n}\n\ninterface Item {\n\tid: string;\n\tmain: number;\n\tcross: number;\n\tfillMain: boolean;\n\tfillCross: boolean;\n\tminMain?: number;\n\tmaxMain?: number;\n\tminCross?: number;\n\tmaxCross?: number;\n\tbaseline?: number;\n\tbaseOff: number;\n}\n\n/** Where the first child starts and the step between children along the main axis. */\nfunction mainOffsets(align: AutoLayoutAlignMain, inner: number, total: number, n: number, gap: number): [number, number] {\n\tconst gaps = gap * Math.max(n - 1, 0);\n\tconst free = inner - total;\n\tconst k = Math.max(n, 1);\n\tswitch (align) {\n\t\tcase 'center':\n\t\t\treturn [(free - gaps) / 2, gap];\n\t\tcase 'end':\n\t\t\treturn [free - gaps, gap];\n\t\tcase 'space-between':\n\t\t\treturn [0, n > 1 ? Math.max(free / (n - 1), 0) : 0];\n\t\tcase 'space-around': {\n\t\t\tconst g = Math.max(free / k, 0);\n\t\t\treturn [g / 2, g];\n\t\t}\n\t\tcase 'space-evenly': {\n\t\t\tconst g = Math.max(free / (k + 1), 0);\n\t\t\treturn [g, g];\n\t\t}\n\t\tdefault:\n\t\t\treturn [0, gap];\n\t}\n}\n\n/**\n * Shares `free` main-axis space among the fill items like CSS flexbox: equal shares within\n * min / max; when clamping adds more than it removes, the items held at their min keep it\n * (otherwise those held at their max do) and the rest share what is left again.\n */\nfunction distribute(items: Item[], open: number[], free: number) {\n\tfree = Math.max(free, 0);\n\twhile (open.length > 0) {\n\t\tconst share = free / open.length;\n\t\tconst sized = open.map((i) => [i, clampLayoutSize(share, items[i].minMain, items[i].maxMain)] as const);\n\t\tconst violation = sized.reduce((sum, [, v]) => sum + (v - share), 0);\n\t\tconst frozen = (v: number) =>\n\t\t\tMath.abs(violation) < 1e-9 || (violation > 0 && v > share + 1e-9) || (violation < 0 && v < share - 1e-9);\n\t\tfor (const [i, v] of sized) {\n\t\t\tif (frozen(v)) {\n\t\t\t\titems[i].main = v;\n\t\t\t\tfree -= v;\n\t\t\t}\n\t\t}\n\t\tfree = Math.max(free, 0);\n\t\topen = sized.filter(([, v]) => !frozen(v)).map(([i]) => i);\n\t}\n}\n\nfunction layout(s: LayoutState, frame: string) {\n\ts.guard(frame, () => layoutFrameInner(s, frame));\n}\n\nfunction layoutFrameInner(s: LayoutState, frame: string) {\n\tconst f = s.boxes.get(frame);\n\tconst al = f && getAutoLayout(f.node);\n\tif (!f || !al) return;\n\tconst horizontal = al.mode !== 'vertical';\n\tconst hugW = getLayoutSizing(f.node, 'width') === 'hug';\n\tconst hugH = getLayoutSizing(f.node, 'height') === 'hug';\n\tconst ow = f.w;\n\tconst oh = f.h;\n\tconst fl = limitsOf(f.node);\n\tconst [pt, pr, pb, pl] = resolvePadding(al.padding);\n\tconst fw = Math.max(clampLayoutSize(ow, fl.minW, fl.maxW), pl + pr);\n\tconst fh = Math.max(clampLayoutSize(oh, fl.minH, fl.maxH), pt + pb);\n\tconst kids = flowChildren(s, frame);\n\n\tfor (const c of kids) {\n\t\tfitHugText(s, c);\n\t\tenforceLimits(s, c);\n\t}\n\n\tif (al.mode === 'grid') {\n\t\tlayoutGrid(s, frame, al);\n\t\treturn;\n\t}\n\n\tconst innerMainFixed = horizontal ? fw - pl - pr : fh - pt - pb;\n\tconst innerCrossFixed = horizontal ? fh - pt - pb : fw - pl - pr;\n\tconst hugMain = horizontal ? hugW : hugH;\n\tconst hugCross = horizontal ? hugH : hugW;\n\tconst alignCross = al.alignCross ?? 'start';\n\tconst baseline = horizontal && alignCross === 'baseline';\n\tconst stretch = alignCross === 'stretch';\n\n\tconst items: Item[] = [];\n\tfor (const c of kids) {\n\t\tconst b = s.boxes.get(c)!;\n\t\tconst lim = limitsOf(b.node);\n\t\tconst sm = getLayoutSizing(b.node, horizontal ? 'width' : 'height');\n\t\tconst sc = getLayoutSizing(b.node, horizontal ? 'height' : 'width');\n\t\titems.push({\n\t\t\tid: c,\n\t\t\tmain: horizontal ? b.w : b.h,\n\t\t\tcross: horizontal ? b.h : b.w,\n\t\t\t// Fill needs room to fill: on an axis the frame hugs it reads as fixed.\n\t\t\tfillMain: sm === 'fill' && !hugMain,\n\t\t\tfillCross: (sc === 'fill' || (stretch && sc !== 'hug')) && !hugCross,\n\t\t\tminMain: horizontal ? lim.minW : lim.minH,\n\t\t\tmaxMain: horizontal ? lim.maxW : lim.maxH,\n\t\t\tminCross: horizontal ? lim.minH : lim.minW,\n\t\t\tmaxCross: horizontal ? lim.maxH : lim.maxW,\n\t\t\tbaseOff: 0\n\t\t});\n\t}\n\n\tconst gap = finite(al.gap);\n\tconst alignMain = al.alignMain ?? 'start';\n\tconst autoGap = alignMain === 'space-between' || alignMain === 'space-around' || alignMain === 'space-evenly';\n\tconst wraps = !!al.wrap && !hugMain;\n\tconst sizeOf = (id: string) => {\n\t\tconst b = s.boxes.get(id)!;\n\t\treturn horizontal ? [b.w, b.h] : [b.h, b.w];\n\t};\n\tconst resize = (it: Item, main: number, cross: number) =>\n\t\tresizeChild(s, it.id, horizontal ? main : cross, horizontal ? cross : main);\n\n\t// Fill across a single line: resize those children first so their main size is final.\n\tif (!wraps) {\n\t\tfor (const it of items) {\n\t\t\tif (!it.fillCross || it.fillMain) continue;\n\t\t\tresize(it, it.main, clampLayoutSize(innerCrossFixed, it.minCross, it.maxCross));\n\t\t\tit.main = sizeOf(it.id)[0];\n\t\t}\n\t}\n\n\t// Split into lines along the main axis.\n\tconst lines: number[][] = [];\n\tif (wraps) {\n\t\tlet cur: number[] = [];\n\t\tlet used = 0;\n\t\titems.forEach((it, i) => {\n\t\t\tconst sz = it.fillMain ? it.minMain ?? 0 : it.main;\n\t\t\tconst add = cur.length === 0 ? sz : Math.max(gap, 0) + sz;\n\t\t\tif (cur.length > 0 && used + add > innerMainFixed + 1e-6) {\n\t\t\t\tlines.push(cur);\n\t\t\t\tcur = [];\n\t\t\t\tused = sz;\n\t\t\t} else {\n\t\t\t\tused += add;\n\t\t\t}\n\t\t\tcur.push(i);\n\t\t});\n\t\tif (cur.length > 0) lines.push(cur);\n\t} else {\n\t\tlines.push(items.map((_, i) => i));\n\t}\n\n\t// Share each line's free space among its fill children.\n\tfor (const line of lines) {\n\t\tconst fixed = line.filter((i) => !items[i].fillMain).reduce((sum, i) => sum + items[i].main, 0);\n\t\tconst gaps = gap * Math.max(line.length - 1, 0);\n\t\tconst fills = line.filter((i) => items[i].fillMain);\n\t\tif (fills.length > 0) distribute(items, fills, innerMainFixed - fixed - (autoGap ? 0 : gaps));\n\t}\n\tfor (const it of items) {\n\t\tif (!it.fillMain) continue;\n\t\tconst cross = it.fillCross && !wraps ? clampLayoutSize(innerCrossFixed, it.minCross, it.maxCross) : it.cross;\n\t\tresize(it, it.main, cross);\n\t\tit.cross = sizeOf(it.id)[1];\n\t}\n\tif (baseline) {\n\t\tfor (const it of items) if (!it.fillCross) it.baseline = itemBaseline(s, it.id, 0);\n\t}\n\n\t// Line extents: [main used, cross size].\n\tconst extents: [number, number][] = [];\n\tfor (const line of lines) {\n\t\tconst gaps = gap * Math.max(line.length - 1, 0);\n\t\tconst lineBase = line.reduce((m, i) => Math.max(m, items[i].baseline ?? 0), 0);\n\t\tfor (const i of line) items[i].baseOff = items[i].baseline !== undefined ? lineBase - items[i].baseline! : 0;\n\t\tconst inLine = line.map((i) => items[i]);\n\t\tconst mainUsed = inLine.reduce((sum, it) => sum + it.main, 0) + gaps;\n\t\t// A line of nothing but fill-across children keeps their own size.\n\t\tconst allFill = inLine.every((it) => it.fillCross);\n\t\tconst cross = inLine.filter((it) => allFill || !it.fillCross).reduce((m, it) => Math.max(m, it.baseOff + it.cross), 0);\n\t\textents.push([mainUsed, cross]);\n\t}\n\n\t// Container size.\n\tconst contentMain = extents.reduce((m, l) => Math.max(m, l[0]), 0);\n\tlet crossGap = al.rowGap !== undefined ? finite(al.rowGap) : gap;\n\tconst contentCross = extents.reduce((sum, l) => sum + l[1], 0) + crossGap * Math.max(lines.length - 1, 0);\n\tlet nw = fw;\n\tlet nh = fh;\n\tif (hugMain) {\n\t\tconst v = contentMain + (horizontal ? pl + pr : pt + pb);\n\t\tif (horizontal) nw = v;\n\t\telse nh = v;\n\t}\n\tif (hugCross) {\n\t\tconst v = contentCross + (horizontal ? pt + pb : pl + pr);\n\t\tif (horizontal) nh = v;\n\t\telse nw = v;\n\t}\n\tnw = Math.max(clampLayoutSize(nw, fl.minW, fl.maxW), pl + pr);\n\tnh = Math.max(clampLayoutSize(nh, fl.minH, fl.maxH), pt + pb);\n\tsetSize(s, frame, nw, nh);\n\tconstrain(s, frame, ow, oh);\n\tconst innerMain = horizontal ? nw - pl - pr : nh - pt - pb;\n\tconst innerCross = horizontal ? nh - pt - pb : nw - pl - pr;\n\n\t// Position. Several lines share the free space across the flow.\n\tlet crossPos = horizontal ? pt : pl;\n\tconst single = lines.length === 1;\n\tif (lines.length > 1) {\n\t\tconst linesCross = extents.reduce((sum, l) => sum + l[1], 0);\n\t\tconst between = lines.length - 1;\n\t\tconst spread = (innerCross - linesCross) / between;\n\t\tconst spaceBetween = al.alignContent === 'space-between';\n\t\tif (spaceBetween && spread > 0) {\n\t\t\tcrossGap = spread;\n\t\t} else {\n\t\t\tif (spaceBetween) crossGap = 0;\n\t\t\tconst free = innerCross - linesCross - crossGap * between;\n\t\t\tif (free > 0 && items.every((it) => it.fillCross)) {\n\t\t\t\tconst extra = free / lines.length;\n\t\t\t\tfor (const l of extents) l[1] += extra;\n\t\t\t} else if (Number.isFinite(free)) {\n\t\t\t\tcrossPos += alignCross === 'center' ? free / 2 : alignCross === 'end' ? free : 0;\n\t\t\t}\n\t\t}\n\t}\n\tlines.forEach((line, li) => {\n\t\tconst [used, ownCross] = extents[li];\n\t\tconst lineCross = single ? innerCross : ownCross;\n\t\tconst total = used - gap * Math.max(line.length - 1, 0);\n\t\tlet [pos, step] = mainOffsets(alignMain, innerMain, total, line.length, gap);\n\t\tpos += horizontal ? pl : pt;\n\t\tfor (const i of line) {\n\t\t\tconst it = items[i];\n\t\t\tconst crossSize = it.fillCross ? clampLayoutSize(lineCross, it.minCross, it.maxCross) : it.cross;\n\t\t\tlet off = 0;\n\t\t\tif (alignCross === 'baseline') off = it.baseOff;\n\t\t\telse if (alignCross === 'center') off = (lineCross - crossSize) / 2;\n\t\t\telse if (alignCross === 'end') off = lineCross - crossSize;\n\t\t\t// Fill across a wrapped line: its size is known only now.\n\t\t\tif (it.fillCross && wraps) resize(it, it.main, crossSize);\n\t\t\tconst x = horizontal ? pos : crossPos + off;\n\t\t\tconst y = horizontal ? crossPos + off : pos;\n\t\t\tplace(s, it.id, finite(x), finite(y));\n\t\t\tpos += it.main + step;\n\t\t}\n\t\tcrossPos += lineCross + crossGap;\n\t});\n}\n\n// ---------------------------------------------------------------------------------------------\n// Grid\n\ninterface TrackSpec {\n\tsizing: AutoLayoutSizing;\n\tvalue: number;\n}\n\nfunction parseTrack(track: AutoLayoutGridTrack | undefined): TrackSpec {\n\tif (typeof track === 'number') return { sizing: 'fixed', value: track };\n\tif (track === 'hug') return { sizing: 'hug', value: 0 };\n\tif (typeof track === 'string') {\n\t\tconst match = /^\\s*(-?\\d*\\.?\\d+)\\s*fr\\s*$/i.exec(track);\n\t\tif (match) return { sizing: 'fill', value: Number(match[1]) };\n\t}\n\tif (track && typeof track === 'object' && 'fr' in track) return { sizing: 'fill', value: track.fr };\n\treturn { sizing: 'fill', value: 1 };\n}\n\nfunction trackList(spec: number | AutoLayoutGridTrack[] | undefined, fallback: number): { count: number; tracks: TrackSpec[] } {\n\tif (Array.isArray(spec)) return { count: Math.min(Math.max(spec.length, 1), MAX_TRACKS), tracks: spec.map(parseTrack) };\n\tconst n = typeof spec === 'number' && Number.isFinite(spec) ? Math.floor(spec) : fallback;\n\treturn { count: Math.min(Math.max(n, 1), MAX_TRACKS), tracks: [] };\n}\n\nfunction trackSpec(tracks: TrackSpec[], i: number): TrackSpec {\n\tconst t = tracks[i] ?? { sizing: 'fill', value: 1 };\n\tconst value = Number.isFinite(t.value) ? Math.min(Math.max(t.value, 0), 1e6) : 0;\n\treturn { sizing: t.sizing, value };\n}\n\n/** A grid child's cell (0-based column / row and spans). */\nexport interface AutoLayoutGridCell {\n\tid: string;\n\tcolumn: number;\n\trow: number;\n\tcolumnSpan: number;\n\trowSpan: number;\n}\n\n/** A grid frame's resolved geometry (frame-local): column x ranges, row y ranges, cells and the size it wants. */\nexport interface AutoLayoutGridGeometry {\n\tcolumns: [number, number][];\n\trows: [number, number][];\n\tcells: AutoLayoutGridCell[];\n\twidth: number;\n\theight: number;\n}\n\nfunction cellRect(g: AutoLayoutGridGeometry, c: AutoLayoutGridCell) {\n\tconst x0 = g.columns[c.column]?.[0] ?? 0;\n\tconst x1 = g.columns[c.column + c.columnSpan - 1]?.[1] ?? x0;\n\tconst y0 = g.rows[c.row]?.[0] ?? 0;\n\tconst y1 = g.rows[c.row + c.rowSpan - 1]?.[1] ?? y0;\n\treturn { x: x0, y: y0, width: Math.max(x1, x0) - x0, height: Math.max(y1, y0) - y0 };\n}\n\nconst toIndex = (v: unknown): number | undefined =>\n\ttypeof v === 'number' && Number.isFinite(v) && v >= 0 ? Math.floor(v) : undefined;\n\n/** Places grid children: anchored ones first, then the rest in reading order into the next free cells. */\nfunction gridPlace(s: LayoutState, al: AutoLayout, kids: string[]): { cells: AutoLayoutGridCell[]; rows: number } {\n\tconst cols = trackList(al.grid?.columns, 2).count;\n\tconst minRows = trackList(al.grid?.rows, 1).count;\n\tconst maxRows = Math.min(Math.max(Math.floor(MAX_GRID_CELLS / cols), 1), MAX_TRACKS);\n\tconst occ: boolean[][] = [];\n\tconst free = (c: number, r: number, cs: number, rs: number) => {\n\t\tfor (let ri = r; ri < r + rs; ri++) {\n\t\t\tconst row = occ[ri];\n\t\t\tif (!row) continue;\n\t\t\tfor (let ci = c; ci < c + cs; ci++) if (row[ci]) return false;\n\t\t}\n\t\treturn true;\n\t};\n\tconst mark = (c: number, r: number, cs: number, rs: number) => {\n\t\twhile (occ.length < r + rs) occ.push(new Array(cols).fill(false));\n\t\tfor (let ri = r; ri < r + rs; ri++) for (let ci = c; ci < c + cs; ci++) occ[ri][ci] = true;\n\t};\n\tconst spans = (id: string) => {\n\t\tconst area = getChildLayout(s.boxes.get(id)!.node).gridArea ?? {};\n\t\tconst cs = Math.min(Math.max(toIndex(area.columnSpan) ?? 1, 1), cols);\n\t\tconst rs = Math.min(Math.max(toIndex(area.rowSpan) ?? 1, 1), maxRows);\n\t\tconst col = toIndex(area.column);\n\t\tconst row = toIndex(area.row);\n\t\treturn {\n\t\t\tcol: col === undefined ? undefined : Math.min(col, cols - cs),\n\t\t\trow: row === undefined ? undefined : Math.min(row, maxRows - rs),\n\t\t\tcs,\n\t\t\trs\n\t\t};\n\t};\n\tconst placed: (AutoLayoutGridCell | undefined)[] = new Array(kids.length).fill(undefined);\n\tkids.forEach((id, i) => {\n\t\tconst { col, row, cs, rs } = spans(id);\n\t\tif (col !== undefined && row !== undefined) {\n\t\t\tmark(col, row, cs, rs);\n\t\t\tplaced[i] = { id, column: col, row, columnSpan: cs, rowSpan: rs };\n\t\t}\n\t});\n\tlet cursor: [number, number] = [0, 0]; // [row, column]\n\tkids.forEach((id, i) => {\n\t\tif (placed[i]) return;\n\t\tconst { col, row, cs, rs } = spans(id);\n\t\tconst lastRow = maxRows - rs;\n\t\tlet c = 0;\n\t\tlet r = 0;\n\t\tif (col !== undefined) {\n\t\t\tr = lastRow;\n\t\t\tfor (let ri = 0; ri <= lastRow; ri++) if (free(col, ri, cs, rs)) { r = ri; break; }\n\t\t\tc = col;\n\t\t} else if (row !== undefined) {\n\t\t\tr = row;\n\t\t\tfor (let ci = 0; ci <= cols - cs; ci++) if (free(ci, row, cs, rs)) { c = ci; break; }\n\t\t} else {\n\t\t\tlet [rr, cc] = cursor;\n\t\t\tlet found: [number, number] | undefined;\n\t\t\twhile (rr <= lastRow) {\n\t\t\t\tif (cc + cs <= cols && free(cc, rr, cs, rs)) {\n\t\t\t\t\tfound = [cc, rr];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcc += 1;\n\t\t\t\tif (cc + cs > cols) {\n\t\t\t\t\tcc = 0;\n\t\t\t\t\trr += 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\t[c, r] = found ?? [0, lastRow];\n\t\t\tcursor = [r, c + cs];\n\t\t}\n\t\tmark(c, r, cs, rs);\n\t\tplaced[i] = { id, column: c, row: r, columnSpan: cs, rowSpan: rs };\n\t});\n\tconst cells = placed.filter((c): c is AutoLayoutGridCell => !!c);\n\tconst rows = Math.max(cells.reduce((m, c) => Math.max(m, c.row + c.rowSpan), 0), minRows);\n\treturn { cells, rows: Math.min(rows, MAX_TRACKS) };\n}\n\n/**\n * Resolves track sizes: fixed tracks keep their size, hug tracks fit their content, fill tracks\n * share what is left by their `fr` like CSS (a track never gets less than its content).\n * `avail` is undefined when the frame hugs that axis.\n */\nfunction resolveTracks(tracks: TrackSpec[], content: number[], avail: number | undefined): number[] {\n\tconst out = content.map((c, i) => {\n\t\tconst t = trackSpec(tracks, i);\n\t\treturn t.sizing === 'fixed' ? t.value : c;\n\t});\n\tif (avail === undefined) return out;\n\tconst fr = (i: number) => trackSpec(tracks, i).value;\n\tlet flex = out.map((_, i) => i).filter((i) => trackSpec(tracks, i).sizing === 'fill');\n\tlet used = out.reduce((sum, v, i) => (trackSpec(tracks, i).sizing === 'fill' ? sum : sum + v), 0);\n\twhile (flex.length > 0) {\n\t\tconst total = flex.reduce((sum, i) => sum + fr(i), 0);\n\t\tconst unit = total > 1e-12 ? Math.max(avail - used, 0) / total : 0;\n\t\tconst open = flex.filter((i) => unit * fr(i) >= out[i]);\n\t\tconst frozen = flex.filter((i) => unit * fr(i) < out[i]);\n\t\tif (frozen.length === 0) {\n\t\t\tfor (const i of open) out[i] = unit * fr(i);\n\t\t\tbreak;\n\t\t}\n\t\tused += frozen.reduce((sum, i) => sum + out[i], 0);\n\t\tflex = open;\n\t}\n\treturn out;\n}\n\n/** Content size per track: the largest single-span child; spanning children grow the content-sized tracks they span. */\nfunction trackContent(\n\tcells: AutoLayoutGridCell[],\n\tsizes: number[],\n\ttracks: TrackSpec[],\n\tn: number,\n\tgap: number,\n\tcolumns: boolean,\n\thug: boolean\n): number[] {\n\tconst byContent = (i: number) => trackSpec(tracks, i).sizing !== 'fixed';\n\tconst content = Array.from({ length: n }, (_, i) => {\n\t\tconst t = trackSpec(tracks, i);\n\t\treturn t.sizing === 'fixed' ? t.value : 0;\n\t});\n\tconst span = (c: AutoLayoutGridCell): [number, number] => (columns ? [c.column, c.columnSpan] : [c.row, c.rowSpan]);\n\tcells.forEach((c, k) => {\n\t\tconst [start, len] = span(c);\n\t\tif (len === 1 && byContent(start) && start < n) content[start] = Math.max(content[start], sizes[k]);\n\t});\n\tcells.forEach((c, k) => {\n\t\tconst [start, len] = span(c);\n\t\tif (len < 2) return;\n\t\tconst range = Array.from({ length: len }, (_, j) => start + j).filter((i) => i < n);\n\t\tif (!hug && range.some((i) => trackSpec(tracks, i).sizing === 'fill')) return;\n\t\tconst have = range.reduce((sum, i) => sum + content[i], 0) + gap * (len - 1);\n\t\tconst grow = range.filter(byContent);\n\t\tif (sizes[k] > have && grow.length > 0) {\n\t\t\tconst extra = (sizes[k] - have) / grow.length;\n\t\t\tfor (const i of grow) content[i] += extra;\n\t\t}\n\t});\n\treturn content;\n}\n\nfunction trackRanges(start: number, sizes: number[], gap: number): [number, number][] {\n\tlet x = start;\n\treturn sizes.map((size) => {\n\t\tconst r: [number, number] = [x, x + size];\n\t\tx += size + gap;\n\t\treturn r;\n\t});\n}\n\nfunction gridGeometryOf(s: LayoutState, frame: string): AutoLayoutGridGeometry | undefined {\n\tconst f = s.boxes.get(frame);\n\tconst al = f && getAutoLayout(f.node);\n\tif (!f || !al || al.mode !== 'grid') return undefined;\n\tconst [pt, pr, pb, pl] = resolvePadding(al.padding);\n\tconst kids = flowChildren(s, frame);\n\tconst { cells, rows } = gridPlace(s, al, kids);\n\tconst colSpec = trackList(al.grid?.columns, 2);\n\tconst rowSpec = trackList(al.grid?.rows, 1);\n\tconst cols = colSpec.count;\n\tconst gx = Math.max(finite(al.gap), 0);\n\tconst gy = Math.max(al.rowGap !== undefined ? finite(al.rowGap) : gx, 0);\n\tconst hugW = getLayoutSizing(f.node, 'width') === 'hug';\n\tconst hugH = getLayoutSizing(f.node, 'height') === 'hug';\n\tconst ws: number[] = [];\n\tconst hs: number[] = [];\n\tfor (const c of cells) {\n\t\tconst b = s.boxes.get(c.id)!;\n\t\tconst lim = limitsOf(b.node);\n\t\tws.push(getLayoutSizing(b.node, 'width') === 'fill' && !hugW ? lim.minW ?? 0 : b.w);\n\t\ths.push(getLayoutSizing(b.node, 'height') === 'fill' && !hugH ? lim.minH ?? 0 : b.h);\n\t}\n\tconst colContent = trackContent(cells, ws, colSpec.tracks, cols, gx, true, hugW);\n\tconst rowContent = trackContent(cells, hs, rowSpec.tracks, rows, gy, false, hugH);\n\tconst gapsW = gx * Math.max(cols - 1, 0);\n\tconst gapsH = gy * Math.max(rows - 1, 0);\n\tconst colW = resolveTracks(colSpec.tracks, colContent, hugW ? undefined : finite(f.w) - pl - pr - gapsW);\n\tconst rowH = resolveTracks(rowSpec.tracks, rowContent, hugH ? undefined : finite(f.h) - pt - pb - gapsH);\n\tconst lim = limitsOf(f.node);\n\tconst width = hugW ? colW.reduce((a, b) => a + b, 0) + gapsW + pl + pr : finite(f.w);\n\tconst height = hugH ? rowH.reduce((a, b) => a + b, 0) + gapsH + pt + pb : finite(f.h);\n\treturn {\n\t\tcolumns: trackRanges(pl, colW, gx),\n\t\trows: trackRanges(pt, rowH, gy),\n\t\tcells,\n\t\twidth: Math.max(clampLayoutSize(width, lim.minW, lim.maxW), pl + pr),\n\t\theight: Math.max(clampLayoutSize(height, lim.minH, lim.maxH), pt + pb)\n\t};\n}\n\nfunction layoutGrid(s: LayoutState, frame: string, al: AutoLayout) {\n\tconst f = s.boxes.get(frame)!;\n\tconst fw = f.w;\n\tconst fh = f.h;\n\tconst hugW = getLayoutSizing(f.node, 'width') === 'hug';\n\tconst hugH = getLayoutSizing(f.node, 'height') === 'hug';\n\tconst lim = limitsOf(f.node);\n\tconst [pt, pr, pb, pl] = resolvePadding(al.padding);\n\tsetSize(\n\t\ts,\n\t\tframe,\n\t\thugW ? fw : Math.max(clampLayoutSize(fw, lim.minW, lim.maxW), pl + pr),\n\t\thugH ? fh : Math.max(clampLayoutSize(fh, lim.minH, lim.maxH), pt + pb)\n\t);\n\tlet g = gridGeometryOf(s, frame);\n\tif (!g) return;\n\t// Fill-width children take their column width first, so wrapping text sizes its row.\n\tlet widened = false;\n\tfor (const c of g.cells) {\n\t\tconst b = s.boxes.get(c.id)!;\n\t\tif (getLayoutSizing(b.node, 'width') === 'fill' && !hugW) {\n\t\t\tconst l = limitsOf(b.node);\n\t\t\tresizeChild(s, c.id, clampLayoutSize(cellRect(g, c).width, l.minW, l.maxW), b.h);\n\t\t\twidened = true;\n\t\t}\n\t}\n\tif (widened) g = gridGeometryOf(s, frame) ?? g;\n\tsetSize(s, frame, g.width, g.height);\n\tconstrain(s, frame, fw, fh);\n\tconst frameX: AutoLayoutCellAlign = al.alignMain === 'center' ? 'center' : al.alignMain === 'end' ? 'end' : 'start';\n\tconst frameY: AutoLayoutCellAlign = al.alignCross === 'center' ? 'center' : al.alignCross === 'end' ? 'end' : 'start';\n\tconst offset = (a: AutoLayoutCellAlign, free: number) => (a === 'center' ? free / 2 : a === 'end' ? free : 0);\n\tfor (const c of g.cells) {\n\t\tconst cell = cellRect(g, c);\n\t\tconst b = s.boxes.get(c.id)!;\n\t\tconst l = limitsOf(b.node);\n\t\tconst area = getChildLayout(b.node).gridArea ?? {};\n\t\tconst fillW = getLayoutSizing(b.node, 'width') === 'fill' && !hugW;\n\t\tconst fillH = getLayoutSizing(b.node, 'height') === 'fill' && !hugH;\n\t\tif (fillW || fillH) {\n\t\t\tresizeChild(\n\t\t\t\ts,\n\t\t\t\tc.id,\n\t\t\t\tfillW ? clampLayoutSize(cell.width, l.minW, l.maxW) : b.w,\n\t\t\t\tfillH ? clampLayoutSize(cell.height, l.minH, l.maxH) : b.h\n\t\t\t);\n\t\t}\n\t\tconst ox = offset(area.alignX ?? frameX, cell.width - b.w);\n\t\tconst oy = offset(area.alignY ?? frameY, cell.height - b.h);\n\t\tplace(s, c.id, finite(cell.x + ox), finite(cell.y + oy));\n\t}\n}\n\n// ---------------------------------------------------------------------------------------------\n// Constraints\n\n/**\n * After `id` changed size from `ow × oh`, moves / resizes its children by their constraints\n * (recursively). In an auto layout frame only absolute children follow constraints.\n */\nfunction constrain(s: LayoutState, id: string, ow: number, oh: number) {\n\tconst f = s.boxes.get(id);\n\tif (!f) return;\n\tconst nw = f.w;\n\tconst nh = f.h;\n\tif (Math.abs(nw - ow) < 1e-9 && Math.abs(nh - oh) < 1e-9) return;\n\tconst absoluteOnly = !!getAutoLayout(f.node);\n\tconst sx = ow > 1e-9 ? nw / ow : 1;\n\tconst sy = oh > 1e-9 ? nh / oh : 1;\n\ts.guard(`constrain:${id}`, () => {\n\t\tfor (const k of f.kids) {\n\t\t\tconst c = s.boxes.get(k)!;\n\t\t\tconst child = getChildLayout(c.node);\n\t\t\tif (absoluteOnly && !child.absolute) continue;\n\t\t\tlet x0 = c.x;\n\t\t\tlet x1 = c.x + c.w;\n\t\t\tlet y0 = c.y;\n\t\t\tlet y1 = c.y + c.h;\n\t\t\tconst dx = nw - ow;\n\t\t\tconst dy = nh - oh;\n\t\t\tswitch (child.constraints?.horizontal ?? 'left') {\n\t\t\t\tcase 'right':\n\t\t\t\t\tx0 += dx;\n\t\t\t\t\tx1 += dx;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'leftRight':\n\t\t\t\t\tx1 += dx;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'center':\n\t\t\t\t\tx0 += dx / 2;\n\t\t\t\t\tx1 += dx / 2;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'scale':\n\t\t\t\t\tx0 *= sx;\n\t\t\t\t\tx1 *= sx;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tswitch (child.constraints?.vertical ?? 'top') {\n\t\t\t\tcase 'bottom':\n\t\t\t\t\ty0 += dy;\n\t\t\t\t\ty1 += dy;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'topBottom':\n\t\t\t\t\ty1 += dy;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'center':\n\t\t\t\t\ty0 += dy / 2;\n\t\t\t\t\ty1 += dy / 2;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'scale':\n\t\t\t\t\ty0 *= sy;\n\t\t\t\t\ty1 *= sy;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tconst cw = c.w;\n\t\t\tconst ch = c.h;\n\t\t\tconst resized = Math.abs(x1 - x0 - cw) > 1e-9 || Math.abs(y1 - y0 - ch) > 1e-9;\n\t\t\tplace(s, k, finite(x0), finite(y0));\n\t\t\tif (resized) {\n\t\t\t\tsetSize(s, k, x1 - x0, y1 - y0);\n\t\t\t\tif (c.kids.length > 0) constrain(s, k, cw, ch);\n\t\t\t\tif (getAutoLayout(c.node)) layout(s, k);\n\t\t\t}\n\t\t}\n\t});\n}\n\n// ---------------------------------------------------------------------------------------------\n// Public API\n\n/**\n * Lays out one auto layout frame's children and sizes the frame when it hugs (nested frames\n * that are resized by the layout are laid out again). Returns the position / size updates.\n */\nexport function layoutFrame(nodes: CanvasNode[], frameId: string, options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\tconst s = new LayoutState(nodes, options);\n\tlayout(s, frameId);\n\treturn s.updates();\n}\n\nfunction postOrder(s: LayoutState, nodes: CanvasNode[]): string[] {\n\tconst out: string[] = [];\n\tconst seen = new Set<string>();\n\tconst visit = (id: string) => {\n\t\tif (seen.has(id)) return;\n\t\tseen.add(id);\n\t\tfor (const k of s.boxes.get(id)?.kids ?? []) visit(k);\n\t\tout.push(id);\n\t};\n\tconst roots = nodes.filter((n) => !n.parentId || !s.boxes.has(n.parentId));\n\tfor (const r of roots) visit(r.id);\n\tfor (const n of nodes) visit(n.id); // nodes in parent cycles\n\treturn out;\n}\n\n/** Lays out every auto layout frame, children before parents, so nested hugging settles in one pass. */\nexport function layoutAll(nodes: CanvasNode[], options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\tconst s = new LayoutState(nodes, options);\n\tfor (const id of postOrder(s, nodes)) if (getAutoLayout(s.boxes.get(id)!.node)) layout(s, id);\n\treturn s.updates();\n}\n\n/**\n * Lays out `changedId` (when it is a frame) and then each of its auto layout ancestors,\n * innermost first. Use it after a node inside a frame was added, removed, resized or moved.\n */\nexport function layoutUp(nodes: CanvasNode[], changedId: string, options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\treturn layoutUpMany(nodes, [changedId], options);\n}\n\n/** {@link layoutUp} for several changed nodes at once (each chain once, innermost frames first). */\nexport function layoutUpMany(nodes: CanvasNode[], changedIds: Iterable<string>, options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\tconst s = new LayoutState(nodes, options);\n\tconst chains = [...new Set(changedIds)].filter((id) => s.boxes.has(id)).map((id) => [id, ...s.ancestors(id)]);\n\t// Deeper frames first so a parent sees its children's final sizes.\n\tconst depth = new Map<string, number>();\n\tfor (const chain of chains) chain.forEach((id, i) => depth.set(id, Math.max(depth.get(id) ?? 0, chain.length - 1 - i)));\n\tconst order = [...depth.keys()].sort((a, b) => depth.get(b)! - depth.get(a)!);\n\tfor (const id of order) if (getAutoLayout(s.boxes.get(id)!.node)) layout(s, id);\n\treturn s.updates();\n}\n\n/**\n * Children of `parentId` follow their constraints after the parent was resized from\n * `previousSize` to its current size. In an auto layout frame only absolute children move\n * (the layout places the others). Returns the updates.\n */\nexport function applyConstraints(\n\tnodes: CanvasNode[],\n\tparentId: string,\n\tpreviousSize: { width: number; height: number },\n\toptions: AutoLayoutOptions = {}\n): CanvasNodeUpdate[] {\n\tconst s = new LayoutState(nodes, options);\n\tconstrain(s, parentId, previousSize.width, previousSize.height);\n\treturn s.updates();\n}\n\n/** The resolved grid of a grid frame (frame-local), from its children's current sizes. */\nexport function getAutoLayoutGrid(\n\tnodes: CanvasNode[],\n\tframeId: string,\n\toptions: AutoLayoutOptions = {}\n): AutoLayoutGridGeometry | undefined {\n\treturn gridGeometryOf(new LayoutState(nodes, options), frameId);\n}\n\n/** Ids of the children a frame places (visible, not absolute), in order. */\nexport function getFlowChildren(nodes: CanvasNode[], frameId: string): string[] {\n\treturn nodes\n\t\t.filter((n) => n.parentId === frameId && n.id !== frameId && n.hidden !== true && !getChildLayout(n).absolute)\n\t\t.map((n) => n.id);\n}\n\n/** The nearest auto layout frame that places `id` (its parent, when `id` is a flow child). */\nexport function getLayoutParent(nodes: CanvasNode[], id: string): CanvasNode | undefined {\n\tconst lookup = createNodeLookup(nodes);\n\tconst node = lookup.get(id);\n\tif (!node?.parentId || getChildLayout(node).absolute) return undefined;\n\tconst parent = lookup.get(node.parentId);\n\treturn isAutoLayoutFrame(parent) ? parent : undefined;\n}\n\n/**\n * Makes a node's sizing coherent with where it sits, and that of its children: `fill` needs an\n * auto layout parent that does not hug that axis (and a flow child), `hug` needs an auto layout\n * frame or a text-like node (see `measure.textSize`); otherwise the axis becomes `fixed`.\n * Writes `data.childLayout.sizing`. Returns a new array (unchanged nodes keep their identity).\n */\nexport function makeSizingCoherent<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tid: string,\n\toptions: AutoLayoutOptions = {}\n): TNode[] {\n\tconst lookup = createNodeLookup(nodes);\n\tconst targets = new Set([id, ...nodes.filter((n) => n.parentId === id).map((n) => n.id)]);\n\tif (!lookup.has(id)) return nodes;\n\tlet changed = false;\n\tconst next = nodes.map((node) => {\n\t\tif (!targets.has(node.id)) return node;\n\t\tconst parent = node.parentId ? lookup.get(node.parentId) : undefined;\n\t\tconst flowParent = parent && isAutoLayoutFrame(parent) && !getChildLayout(node).absolute ? parent : undefined;\n\t\tconst canHug = isAutoLayoutFrame(node) || options.measure?.textSize?.(node) !== undefined;\n\t\tconst coherent = (axis: Axis): AutoLayoutSizing => {\n\t\t\tconst sizing = getLayoutSizing(node, axis);\n\t\t\tif (sizing === 'fill' && (!flowParent || getLayoutSizing(flowParent, axis) === 'hug')) return 'fixed';\n\t\t\tif (sizing === 'hug' && !canHug) return 'fixed';\n\t\t\treturn sizing;\n\t\t};\n\t\tconst width = coherent('width');\n\t\tconst height = coherent('height');\n\t\tif (width === getLayoutSizing(node, 'width') && height === getLayoutSizing(node, 'height')) return node;\n\t\tchanged = true;\n\t\tconst child = getChildLayout(node);\n\t\treturn {\n\t\t\t...node,\n\t\t\tdata: { ...node.data, [CHILD_LAYOUT_DATA_KEY]: { ...child, sizing: { ...child.sizing, width, height } } }\n\t\t};\n\t});\n\treturn changed ? next : nodes;\n}\n\n// ---------------------------------------------------------------------------------------------\n// Reordering\n\nexport interface AutoLayoutInsertTarget {\n\tframeId: string;\n\t/** Index among the frame's flow children, not counting the dragged node. */\n\tindex: number;\n\t/** Insertion marker (absolute flow coordinates) for `<CanvasGuidesLayer>`. */\n\tguide: GuideLine;\n}\n\n/**\n * Where a node dropped at `point` (absolute flow coordinates) goes among the flow children of\n * auto layout frame `frameId`: the reading-order index of the slot under the point. `excludeId`\n * (the dragged node) is left out of the siblings.\n */\nexport function getAutoLayoutInsertTarget(\n\tnodes: CanvasNode[],\n\tframeId: string,\n\tpoint: XYPosition,\n\toptions: AutoLayoutOptions & { excludeId?: string } = {}\n): AutoLayoutInsertTarget | undefined {\n\tconst lookup = createNodeLookup(nodes);\n\tconst frame = lookup.get(frameId);\n\tconst al = getAutoLayout(frame);\n\tif (!frame || !al) return undefined;\n\tconst s = new LayoutState(nodes, options);\n\tconst origin = getAbsolutePosition(frame, lookup);\n\tconst px = point.x - origin.x;\n\tconst py = point.y - origin.y;\n\tconst siblings = flowChildren(s, frameId)\n\t\t.filter((id) => id !== options.excludeId)\n\t\t.map((id) => s.boxes.get(id)!);\n\t// Grid and wrapped layouts read in rows (vertical wrap: columns); plain stacks along their axis.\n\tconst horizontal = al.mode !== 'vertical';\n\tconst main = (b: { x: number; y: number }) => (horizontal ? b.x : b.y);\n\tconst mainSize = (b: Box) => (horizontal ? b.w : b.h);\n\tconst cross = (b: { x: number; y: number }) => (horizontal ? b.y : b.x);\n\tconst crossSize = (b: Box) => (horizontal ? b.h : b.w);\n\tconst p = { x: px, y: py };\n\tconst multiLine = al.mode === 'grid' || !!al.wrap;\n\tlet index = 0;\n\tfor (const b of siblings) {\n\t\tconst before = multiLine\n\t\t\t? cross(b) + crossSize(b) <= cross(p) ||\n\t\t\t\t(cross(b) <= cross(p) && main(b) + mainSize(b) / 2 < main(p))\n\t\t\t: main(b) + mainSize(b) / 2 < main(p);\n\t\tif (before) index += 1;\n\t}\n\tconst [pt, pr, pb, pl] = resolvePadding(al.padding);\n\tconst prev = siblings[index - 1];\n\tconst next = siblings[index];\n\tconst f = s.boxes.get(frameId)!;\n\tlet guide: GuideLine;\n\tif (horizontal) {\n\t\tconst x = prev && next && Math.abs(cross(prev) - cross(next)) < 1e-6\n\t\t\t? (prev.x + prev.w + next.x) / 2\n\t\t\t: prev\n\t\t\t\t? prev.x + prev.w\n\t\t\t\t: next\n\t\t\t\t\t? next.x\n\t\t\t\t\t: pl;\n\t\tconst ref = next ?? prev;\n\t\tconst y1 = ref ? ref.y : pt;\n\t\tconst y2 = ref ? ref.y + ref.h : f.h - pb;\n\t\tguide = { orientation: 'vertical', kind: 'edge', x1: origin.x + x, y1: origin.y + y1, x2: origin.x + x, y2: origin.y + y2 };\n\t} else {\n\t\tconst y = prev && next && Math.abs(cross(prev) - cross(next)) < 1e-6\n\t\t\t? (prev.y + prev.h + next.y) / 2\n\t\t\t: prev\n\t\t\t\t? prev.y + prev.h\n\t\t\t\t: next\n\t\t\t\t\t? next.y\n\t\t\t\t\t: pt;\n\t\tconst ref = next ?? prev;\n\t\tconst x1 = ref ? ref.x : pl;\n\t\tconst x2 = ref ? ref.x + ref.w : f.w - pr;\n\t\tguide = { orientation: 'horizontal', kind: 'edge', x1: origin.x + x1, y1: origin.y + y, x2: origin.x + x2, y2: origin.y + y };\n\t}\n\treturn { frameId, index, guide };\n}\n\n/**\n * Moves child `childId` of an auto layout frame to flow index `index` (among its flow siblings)\n * by reordering the node array; parents still precede their children. Returns a new array\n * (the same array when nothing moves).\n */\nexport function moveLayoutChild<TNode extends CanvasNode>(nodes: TNode[], childId: string, index: number): TNode[] {\n\tconst child = nodes.find((n) => n.id === childId);\n\tif (!child?.parentId) return nodes;\n\tconst siblings = nodes.filter((n) => n.parentId === child.parentId && n.id !== child.parentId);\n\tconst flow = siblings.filter((n) => n.hidden !== true && !getChildLayout(n).absolute && n.id !== childId);\n\tconst clamped = Math.min(Math.max(Math.floor(Number.isFinite(index) ? index : 0), 0), flow.length);\n\t// Insert before the flow sibling now at `clamped` (or after the last one).\n\tconst order = siblings.filter((n) => n.id !== childId);\n\tconst anchor = flow[clamped];\n\tconst at = anchor ? order.indexOf(anchor) : flow.length > 0 ? order.indexOf(flow[flow.length - 1]) + 1 : order.length;\n\torder.splice(at, 0, child);\n\tif (order.every((n, i) => n === siblings[i])) return nodes;\n\t// Put the siblings back into the slots they occupied, in their new order.\n\tlet k = 0;\n\tconst parentId = child.parentId;\n\tconst placed = nodes.map((n) => (n.parentId === parentId && n.id !== parentId ? order[k++] : n));\n\treturn parentsFirst(placed);\n}\n\n/** Stable reorder so every node comes after its parent (xyflow requires it). */\nfunction parentsFirst<TNode extends CanvasNode>(nodes: TNode[]): TNode[] {\n\tconst ids = new Set(nodes.map((n) => n.id));\n\tconst pending = new Map<string, TNode[]>();\n\tconst done = new Set<string>();\n\tconst out: TNode[] = [];\n\tconst emit = (n: TNode) => {\n\t\tout.push(n);\n\t\tdone.add(n.id);\n\t\tconst waiting = pending.get(n.id);\n\t\tif (waiting) {\n\t\t\tpending.delete(n.id);\n\t\t\tfor (const w of waiting) emit(w);\n\t\t}\n\t};\n\tfor (const n of nodes) {\n\t\tif (n.parentId && ids.has(n.parentId) && !done.has(n.parentId) && n.parentId !== n.id) {\n\t\t\tif (!pending.has(n.parentId)) pending.set(n.parentId, []);\n\t\t\tpending.get(n.parentId)!.push(n);\n\t\t} else emit(n);\n\t}\n\tfor (const waiting of pending.values()) for (const n of waiting) if (!done.has(n.id)) emit(n); // cycles\n\treturn out;\n}\n","import type { Command } from '../history/types.js';\nimport {\n\talignNodes,\n\tapplyNodeUpdates,\n\tdistributeNodes,\n\tinvertNodeUpdates,\n\tisLocked,\n\tlockNodes,\n\tmatchSize,\n\treorderNodes,\n\tunlockNodes,\n\ttype AlignEdge,\n\ttype AlignNodesOptions,\n\ttype CanvasNodeUpdate,\n\ttype DistributeAxis,\n\ttype DistributeMode,\n\ttype DistributeNodesOptions,\n\ttype MatchSizeOptions,\n\ttype NodeRectOptions,\n\ttype SizeDimension,\n\ttype ZOrderOperation\n} from './arrange.js';\nimport type { CanvasArrangeAction } from './arrangeShortcuts.js';\nimport {\n\tapplyShapeCombination,\n\tapplyShapeReplacement,\n\tcanCombineShapeNodes,\n\tcanOffsetShapeNodes,\n\tcanOutlineShapeNodeStrokes,\n\tcombineShapeNodes,\n\toffsetShapeNodes,\n\toutlineShapeNodeStrokes,\n\trevertShapeCombination,\n\ttype CombineShapeNodesOptions,\n\ttype CombineShapeNodesResult,\n\ttype OffsetShapeNodesOptions,\n\ttype OutlineShapeStrokesOptions,\n\ttype ShapeOperation,\n\ttype ShapePathEditResult\n} from './shapeNodes.js';\nimport {\n\tcreateNodeGuideIndex,\n\tsnapNodeDrag,\n\ttype GuideIndex,\n\ttype GuideLine,\n\ttype SnapGuidesOptions,\n\ttype SnapNodeDragResult\n} from './guides.js';\nimport {\n\tgetResizingNodeRect,\n\tinferResizeHandle,\n\tsnapNodeResize,\n\ttype ResizeHandle,\n\ttype SnapNodeResizeResult\n} from './resizeSnap.js';\nimport { createNodeLookup } from './arrange.js';\nimport { layoutUpMany, type AutoLayoutOptions } from './autoLayout.js';\nimport type { CanvasNode, Rect, XYPosition } from './types.js';\n\n/** Minimal history surface (`HistoryManager` satisfies it). */\nexport interface ArrangeHistoryTarget {\n\texecute(command: Command): void;\n}\n\nexport interface CanvasArrangeControllerOptions<TNode extends CanvasNode = CanvasNode> {\n\tgetNodes: () => TNode[];\n\tsetNodes: (nodes: TNode[]) => void;\n\t/** Ids acted on when a method is called without ids (default: nodes with `selected: true`). */\n\tgetSelectedIds?: () => string[];\n\t/** When set, every arrange action is recorded as one undoable command. */\n\thistory?: ArrangeHistoryTarget;\n\t/** Current zoom, used to convert the guide threshold from screen pixels (default 1). */\n\tgetZoom?: () => number;\n\t/** Smart-guide options (threshold in screen px, edges / centres / spacing toggles). */\n\tguides?: Omit<SnapGuidesOptions, 'zoom'>;\n\t/** Turns drag snapping off while keeping the arrange actions (default `true`). */\n\tsnapping?: boolean;\n\t/** Turns resize snapping off (default: follows `snapping`). */\n\tresizeSnapping?: boolean;\n\t/** Resize options passed to `snapResize` (`keepAspectRatio`, `minWidth`, `minHeight`). */\n\tresize?: { keepAspectRatio?: boolean; minWidth?: number; minHeight?: number };\n\t/** Size resolution for unmeasured nodes. */\n\trectOptions?: NodeRectOptions;\n\t/** Called whenever the guide segments change (set while dragging, cleared on drop). */\n\tonGuidesChange?: (guides: GuideLine[]) => void;\n\t/**\n\t * Re-run auto layout (`data.layout` frames) around the nodes an arrange action moves or\n\t * resizes, in the same undoable step. `true` uses the default measuring.\n\t */\n\tautoLayout?: boolean | AutoLayoutOptions;\n}\n\n/**\n * A React Flow / SvelteFlow style node change; only `position` changes are inspected.\n * `id` is optional because a batch can contain changes without one (React Flow's\n * `add` change carries an `item` instead), so `NodeChange[]` must be accepted as is.\n */\nexport interface PositionChangeLike {\n\ttype: string;\n\tid?: string;\n\tposition?: XYPosition;\n\tdragging?: boolean;\n\t[key: string]: any;\n}\n\n/**\n * Framework-agnostic arrange controller shared by the Svelte rune and the React hook:\n * align / distribute / match size / lock / z-order actions on the selection (optionally\n * undoable through a `HistoryManager`) and smart-guide snapping during drags.\n */\nexport function createCanvasArrangeController<TNode extends CanvasNode = CanvasNode>(\n\toptions: CanvasArrangeControllerOptions<TNode>\n) {\n\tlet guides: GuideLine[] = [];\n\tlet dragIndex: GuideIndex | null = null;\n\tlet dragKey = '';\n\t// Snapped positions from the last dragging batch, re-applied to the drag-stop change.\n\tlet lastSnapped = new Map<string, XYPosition>();\n\t// Resize snapping: the node, its box when the resize started, cached targets and the\n\t// last snapped box (absolute) re-applied when the flow writes the raw end size.\n\tlet resizeId: string | null = null;\n\tlet resizeStart: Rect | null = null;\n\tlet resizeIndex: GuideIndex | null = null;\n\tlet lastResized: SnapNodeResizeResult | null = null;\n\n\tconst selectedIds = (ids?: Iterable<string>) =>\n\t\tids ? [...ids] : options.getSelectedIds?.() ?? options.getNodes().filter((n) => n.selected).map((n) => n.id);\n\n\tconst setGuides = (next: GuideLine[]) => {\n\t\tif (next.length === 0 && guides.length === 0) return;\n\t\tguides = next;\n\t\toptions.onGuidesChange?.(guides);\n\t};\n\n\t/** Applies updates as one (optionally undoable) step. Returns the updates. */\n\tfunction apply(name: string, updates: CanvasNodeUpdate[]): CanvasNodeUpdate[] {\n\t\tif (updates.length === 0) return updates;\n\t\tif (options.autoLayout) {\n\t\t\tconst layoutOptions = options.autoLayout === true ? { rectOptions: options.rectOptions } : options.autoLayout;\n\t\t\tconst moved = applyNodeUpdates(options.getNodes(), updates);\n\t\t\tconst layout = layoutUpMany(moved, updates.map((u) => u.id), layoutOptions);\n\t\t\tif (layout.length > 0) {\n\t\t\t\tconst byId = new Map<string, CanvasNodeUpdate>();\n\t\t\t\tfor (const u of [...updates, ...layout]) byId.set(u.id, { ...byId.get(u.id), ...u });\n\t\t\t\tupdates = [...byId.values()];\n\t\t\t}\n\t\t}\n\t\tconst inverse = invertNodeUpdates(options.getNodes(), updates);\n\t\tconst run = (u: CanvasNodeUpdate[]) => options.setNodes(applyNodeUpdates(options.getNodes(), u));\n\t\tif (options.history) {\n\t\t\toptions.history.execute({ name, execute: () => run(updates), undo: () => run(inverse) });\n\t\t} else {\n\t\t\trun(updates);\n\t\t}\n\t\treturn updates;\n\t}\n\n\tfunction setLocked(name: string, ids: string[], locked: boolean): string[] {\n\t\tconst nodes = options.getNodes();\n\t\tconst set = new Set(ids);\n\t\tconst changed = nodes.filter((n) => set.has(n.id) && isLocked(n) !== locked).map((n) => n.id);\n\t\tif (changed.length === 0) return changed;\n\t\tconst lock = () => options.setNodes(lockNodes(options.getNodes(), changed));\n\t\tconst unlock = () => options.setNodes(unlockNodes(options.getNodes(), changed));\n\t\tif (options.history) {\n\t\t\toptions.history.execute({ name, execute: locked ? lock : unlock, undo: locked ? unlock : lock });\n\t\t} else if (locked) lock();\n\t\telse unlock();\n\t\treturn changed;\n\t}\n\n\tconst rect = () => options.rectOptions ?? {};\n\n\tconst controller = {\n\t\tget guides() {\n\t\t\treturn guides;\n\t\t},\n\t\tapply,\n\t\talign(edge: AlignEdge, ids?: Iterable<string>, alignOptions: AlignNodesOptions = {}) {\n\t\t\treturn apply(`Align ${edge}`, alignNodes(options.getNodes(), selectedIds(ids), edge, { ...rect(), ...alignOptions }));\n\t\t},\n\t\tdistribute(\n\t\t\taxis: DistributeAxis,\n\t\t\tmode: DistributeMode = 'gaps',\n\t\t\tids?: Iterable<string>,\n\t\t\tdistributeOptions: DistributeNodesOptions = {}\n\t\t) {\n\t\t\treturn apply(\n\t\t\t\t`Distribute ${axis}`,\n\t\t\t\tdistributeNodes(options.getNodes(), selectedIds(ids), axis, mode, { ...rect(), ...distributeOptions })\n\t\t\t);\n\t\t},\n\t\tmatchSize(dimension: SizeDimension, ids?: Iterable<string>, sizeOptions: MatchSizeOptions = {}) {\n\t\t\treturn apply(\n\t\t\t\t'Make Same Size',\n\t\t\t\tmatchSize(options.getNodes(), selectedIds(ids), dimension, { ...rect(), ...sizeOptions })\n\t\t\t);\n\t\t},\n\t\treorder(operation: ZOrderOperation, ids?: Iterable<string>) {\n\t\t\treturn apply(operation, reorderNodes(options.getNodes(), selectedIds(ids), operation, rect()));\n\t\t},\n\t\tbringForward: (ids?: Iterable<string>) => controller.reorder('bringForward', ids),\n\t\tsendBackward: (ids?: Iterable<string>) => controller.reorder('sendBackward', ids),\n\t\tbringToFront: (ids?: Iterable<string>) => controller.reorder('bringToFront', ids),\n\t\tsendToBack: (ids?: Iterable<string>) => controller.reorder('sendToBack', ids),\n\t\tlock: (ids?: Iterable<string>) => setLocked('Lock', selectedIds(ids), true),\n\t\tunlock: (ids?: Iterable<string>) => setLocked('Unlock', selectedIds(ids), false),\n\t\ttoggleLock(ids?: Iterable<string>) {\n\t\t\tconst list = selectedIds(ids);\n\t\t\tconst set = new Set(list);\n\t\t\tconst anyUnlocked = options.getNodes().some((n) => set.has(n.id) && !isLocked(n));\n\t\t\treturn anyUnlocked ? controller.lock(list) : controller.unlock(list);\n\t\t},\n\t\tisLocked: (id: string) => isLocked(options.getNodes().find((n) => n.id === id)),\n\n\t\t/**\n\t\t * Freeform shape operation (Unite / Intersect / Subtract / Exclude / Divide) on the\n\t\t * shape nodes among `ids` (default: the selection). Replaces them with the result as\n\t\t * one undoable step. Refuses without changing anything when a selected shape is\n\t\t * locked, fewer than two shapes are selected, or the result is empty.\n\t\t */\n\t\tcombineShapes(\n\t\t\toperation: ShapeOperation,\n\t\t\tids?: Iterable<string>,\n\t\t\tcombineOptions: CombineShapeNodesOptions = {}\n\t\t): CombineShapeNodesResult<TNode> {\n\t\t\tconst before = options.getNodes();\n\t\t\tconst result = combineShapeNodes(before, selectedIds(ids), operation, { ...rect(), ...combineOptions });\n\t\t\tif (!result.ok) return result;\n\t\t\tconst name = `${operation[0].toUpperCase()}${operation.slice(1)} Shapes`;\n\t\t\tconst run = () => options.setNodes(applyShapeCombination(options.getNodes(), result));\n\t\t\tconst undo = () => options.setNodes(revertShapeCombination(options.getNodes(), result, before));\n\t\t\tif (options.history) options.history.execute({ name, execute: run, undo });\n\t\t\telse run();\n\t\t\treturn result;\n\t\t},\n\t\tunite: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('unite', ids, o),\n\t\tintersect: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('intersect', ids, o),\n\t\tsubtract: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('subtract', ids, o),\n\t\texclude: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('exclude', ids, o),\n\t\tdivide: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('divide', ids, o),\n\t\t/** Whether the shape operations can run on `ids` (default: the selection). */\n\t\tcanCombineShapes: (ids?: Iterable<string>) => canCombineShapeNodes(options.getNodes(), selectedIds(ids)).ok,\n\n\t\t/**\n\t\t * Offset Path: grows (`distance > 0`) or shrinks (`distance < 0`) every shape among\n\t\t * `ids` (default: the selection) by `distance` flow units, keeping curves, as one\n\t\t * undoable step. Refuses without changing anything when a shape is locked, no shape is\n\t\t * selected or every result is empty.\n\t\t */\n\t\toffsetShapes(\n\t\t\tdistance: number,\n\t\t\tids?: Iterable<string>,\n\t\t\toffsetOptions: OffsetShapeNodesOptions = {}\n\t\t): ShapePathEditResult<TNode> {\n\t\t\tconst before = options.getNodes();\n\t\t\tconst result = offsetShapeNodes(before, selectedIds(ids), distance, { ...rect(), ...offsetOptions });\n\t\t\tif (!result.ok) return result;\n\t\t\tconst run = () => options.setNodes(applyShapeReplacement(options.getNodes(), result));\n\t\t\tconst undo = () => options.setNodes(revertShapeCombination(options.getNodes(), result, before));\n\t\t\tif (options.history) options.history.execute({ name: 'Offset Path', execute: run, undo });\n\t\t\telse run();\n\t\t\treturn result;\n\t\t},\n\t\t/** Whether `offsetShapes` can run on `ids` (default: the selection). */\n\t\tcanOffsetShapes: (ids?: Iterable<string>) => canOffsetShapeNodes(options.getNodes(), selectedIds(ids)).ok,\n\n\t\t/**\n\t\t * Outline Stroke: converts the strokes of the shapes among `ids` (default: the\n\t\t * selection) to filled path shapes, as one undoable step. Refuses when a shape is\n\t\t * locked or no selected shape has a visible stroke.\n\t\t */\n\t\toutlineStroke(ids?: Iterable<string>, outlineOptions: OutlineShapeStrokesOptions = {}): ShapePathEditResult<TNode> {\n\t\t\tconst before = options.getNodes();\n\t\t\tconst result = outlineShapeNodeStrokes(before, selectedIds(ids), { ...rect(), ...outlineOptions });\n\t\t\tif (!result.ok) return result;\n\t\t\tconst run = () => options.setNodes(applyShapeReplacement(options.getNodes(), result));\n\t\t\tconst undo = () => options.setNodes(revertShapeCombination(options.getNodes(), result, before));\n\t\t\tif (options.history) options.history.execute({ name: 'Outline Stroke', execute: run, undo });\n\t\t\telse run();\n\t\t\treturn result;\n\t\t},\n\t\t/** Whether `outlineStroke` can run on `ids` (default: the selection). */\n\t\tcanOutlineStroke: (ids?: Iterable<string>) => canOutlineShapeNodeStrokes(options.getNodes(), selectedIds(ids)).ok,\n\n\t\t/**\n\t\t * Runs a shortcut action. Scene actions are not handled here (see the scenes store);\n\t\t * returns `false` for them and for unknown actions.\n\t\t */\n\t\trunAction(action: CanvasArrangeAction): boolean {\n\t\t\tswitch (action) {\n\t\t\t\tcase 'alignLeft': controller.align('left'); return true;\n\t\t\t\tcase 'alignCenter': controller.align('center'); return true;\n\t\t\t\tcase 'alignRight': controller.align('right'); return true;\n\t\t\t\tcase 'alignTop': controller.align('top'); return true;\n\t\t\t\tcase 'alignMiddle': controller.align('middle'); return true;\n\t\t\t\tcase 'alignBottom': controller.align('bottom'); return true;\n\t\t\t\tcase 'distributeHorizontal': controller.distribute('horizontal'); return true;\n\t\t\t\tcase 'distributeVertical': controller.distribute('vertical'); return true;\n\t\t\t\tcase 'lock': controller.lock(); return true;\n\t\t\t\tcase 'unlock': controller.unlock(); return true;\n\t\t\t\tcase 'bringForward': controller.bringForward(); return true;\n\t\t\t\tcase 'sendBackward': controller.sendBackward(); return true;\n\t\t\t\tcase 'bringToFront': controller.bringToFront(); return true;\n\t\t\t\tcase 'sendToBack': controller.sendToBack(); return true;\n\t\t\t\tdefault: return false;\n\t\t\t}\n\t\t},\n\n\t\t/** Caches the snap targets for a drag. Optional: `drag` builds them lazily. */\n\t\tbeginDrag(ids?: Iterable<string>) {\n\t\t\tconst list = selectedIds(ids);\n\t\t\tdragKey = list.slice().sort().join('\\u0000');\n\t\t\tdragIndex = createNodeGuideIndex(options.getNodes(), list, rect());\n\t\t},\n\n\t\t/**\n\t\t * Snaps the dragged nodes (as currently positioned in `getNodes()`) to the other\n\t\t * nodes, writes the snapped positions back through `setNodes` (not recorded in\n\t\t * history) and updates `guides`.\n\t\t */\n\t\tdrag(ids?: Iterable<string>): SnapNodeDragResult | null {\n\t\t\tif (options.snapping === false) return null;\n\t\t\tconst list = selectedIds(ids);\n\t\t\tconst key = list.slice().sort().join('\\u0000');\n\t\t\tif (!dragIndex || key !== dragKey) controller.beginDrag(list);\n\t\t\tconst result = snapNodeDrag(options.getNodes(), list, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: dragIndex!\n\t\t\t});\n\t\t\tlastSnapped = new Map();\n\t\t\tif (result.updates.length > 0) {\n\t\t\t\toptions.setNodes(applyNodeUpdates(options.getNodes(), result.updates));\n\t\t\t\tfor (const u of result.updates) if (u.position) lastSnapped.set(u.id, u.position);\n\t\t\t}\n\t\t\tsetGuides(result.guides);\n\t\t\treturn result;\n\t\t},\n\n\t\t/**\n\t\t * React Flow style: adjusts dragging `position` changes in an `onNodesChange` batch so\n\t\t * the dragged nodes snap, and updates `guides`. Returns the (possibly new) changes array.\n\t\t * A batch without a dragging position change ends the drag and clears the guides.\n\t\t */\n\t\tsnapPositionChanges<TChange extends PositionChangeLike>(changes: TChange[]): TChange[] {\n\t\t\tconst dragging = changes.filter(\n\t\t\t\t(c): c is TChange & { id: string; position: XYPosition } =>\n\t\t\t\t\tc.type === 'position' && typeof c.id === 'string' && !!c.position && !!c.dragging\n\t\t\t);\n\t\t\tif (dragging.length === 0) {\n\t\t\t\tif (!changes.some((c) => c.type === 'position' && c.dragging === false)) return changes;\n\t\t\t\t// React Flow's drag-stop change carries the raw pointer position, which would\n\t\t\t\t// undo the snap of the last frame; keep the snapped position instead.\n\t\t\t\tconst snapped = lastSnapped;\n\t\t\t\tlastSnapped = new Map(); // applied to the changes below, not by endDrag\n\t\t\t\tcontroller.endDrag();\n\t\t\t\tconst keep = (c: TChange) =>\n\t\t\t\t\tc.type === 'position' && c.dragging === false && !!c.position && c.id !== undefined && snapped.has(c.id);\n\t\t\t\tif (!changes.some(keep)) return changes;\n\t\t\t\treturn changes.map((c) => (keep(c) ? { ...c, position: snapped.get(c.id!)! } : c));\n\t\t\t}\n\t\t\tif (options.snapping === false) return changes;\n\t\t\tconst ids = dragging.map((c) => c.id);\n\t\t\tconst key = ids.slice().sort().join('\\u0000');\n\t\t\tif (!dragIndex || key !== dragKey) controller.beginDrag(ids);\n\t\t\tconst proposed = new Map(dragging.map((c) => [c.id, c.position]));\n\t\t\tconst nodes = options.getNodes().map((n) => (proposed.has(n.id) ? { ...n, position: proposed.get(n.id)! } : n));\n\t\t\tconst result = snapNodeDrag(nodes, ids, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: dragIndex!\n\t\t\t});\n\t\t\tsetGuides(result.guides);\n\t\t\tconst { x: dx, y: dy } = result.offset;\n\t\t\tlastSnapped = new Map();\n\t\t\tif (dx === 0 && dy === 0) return changes;\n\t\t\t// Children of a dragged parent move with it; only shift top-level dragged nodes.\n\t\t\tconst parents = new Map(nodes.map((n) => [n.id, n.parentId]));\n\t\t\tconst shift = new Set(ids.filter((id) => !proposed.has(parents.get(id) ?? '')));\n\t\t\treturn changes.map((c) => {\n\t\t\t\tif (!(c.type === 'position' && c.position && c.dragging && c.id !== undefined && shift.has(c.id))) return c;\n\t\t\t\tconst position = { x: c.position.x + dx, y: c.position.y + dy };\n\t\t\t\tlastSnapped.set(c.id, position);\n\t\t\t\treturn { ...c, position };\n\t\t\t});\n\t\t},\n\n\t\t/**\n\t\t * Starts resize snapping for node `id`: records its box (to tell which handle is\n\t\t * dragged) and caches the snap targets. Optional: `resize` starts lazily.\n\t\t */\n\t\tbeginResize(id: string) {\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tconst node = lookup.get(id);\n\t\t\tresizeId = id;\n\t\t\tresizeStart = node ? getResizingNodeRect(node, lookup, rect()) : null;\n\t\t\tresizeIndex = createNodeGuideIndex(nodes, [id], rect());\n\t\t\tlastResized = null;\n\t\t},\n\n\t\t/**\n\t\t * Snaps node `id` as currently sized in `getNodes()` (call it after the flow wrote the\n\t\t * new size, e.g. SvelteFlow's `NodeResizer` `onResize`, deferred by a microtask) and\n\t\t * writes the snapped box back through `setNodes` (not recorded in history). `handle`\n\t\t * defaults to the one inferred from the edges that moved since `beginResize`. A locked\n\t\t * node is restored to its starting box.\n\t\t */\n\t\tresize(id: string, handle?: ResizeHandle): SnapNodeResizeResult | null {\n\t\t\tif (resizeId !== id) controller.beginResize(id);\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tconst node = lookup.get(id);\n\t\t\tif (!node || !resizeStart) return null;\n\t\t\tconst proposed = getResizingNodeRect(node, lookup, rect());\n\t\t\tif (isLocked(node)) {\n\t\t\t\tconst start = resizeStart;\n\t\t\t\tconst parent = node.parentId ? lookup.get(node.parentId) : undefined;\n\t\t\t\tconst origin = parent ? getResizingNodeRect(parent, lookup, rect()) : { x: 0, y: 0 };\n\t\t\t\tif (proposed.x !== start.x || proposed.y !== start.y || proposed.width !== start.width || proposed.height !== start.height) {\n\t\t\t\t\toptions.setNodes(\n\t\t\t\t\t\tapplyNodeUpdates(nodes, [\n\t\t\t\t\t\t\t{ id, position: { x: start.x - origin.x, y: start.y - origin.y }, width: start.width, height: start.height }\n\t\t\t\t\t\t])\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tif ((options.resizeSnapping ?? options.snapping) === false) return null;\n\t\t\tconst h = handle ?? inferResizeHandle(resizeStart, proposed);\n\t\t\tif (!h) {\n\t\t\t\tsetGuides([]);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst result = snapNodeResize(nodes, id, proposed, h, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\t...options.resize,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: resizeIndex!\n\t\t\t});\n\t\t\tlastResized = result.updates.length > 0 ? result : null;\n\t\t\tif (result.updates.length > 0) options.setNodes(applyNodeUpdates(nodes, result.updates));\n\t\t\tsetGuides(result.guides);\n\t\t\treturn result;\n\t\t},\n\n\t\t/**\n\t\t * Ends resize snapping: clears guides and re-applies the last snapped box if the flow\n\t\t * wrote the raw pointer size afterwards.\n\t\t */\n\t\tendResize() {\n\t\t\tconst last = lastResized;\n\t\t\tresizeId = null;\n\t\t\tresizeStart = null;\n\t\t\tresizeIndex = null;\n\t\t\tlastResized = null;\n\t\t\tsetGuides([]);\n\t\t\tconst update = last?.updates[0];\n\t\t\tif (!update) return;\n\t\t\tconst node = options.getNodes().find((n) => n.id === update.id);\n\t\t\tif (!node) return;\n\t\t\tconst drift =\n\t\t\t\tnode.position.x !== update.position!.x ||\n\t\t\t\tnode.position.y !== update.position!.y ||\n\t\t\t\tnode.width !== update.width ||\n\t\t\t\tnode.height !== update.height;\n\t\t\tif (drift) options.setNodes(applyNodeUpdates(options.getNodes(), [update]));\n\t\t},\n\n\t\t/**\n\t\t * React Flow style: snaps the `dimensions` (and matching `position`) changes a\n\t\t * `NodeResizer` emits in an `onNodesChange` batch and updates `guides`. The resize-end\n\t\t * change (`resizing: false`) carries the raw pointer size, so the last snapped size is\n\t\t * kept. Resize changes of locked nodes are dropped. Returns the (possibly new) changes.\n\t\t */\n\t\tsnapResizeChanges<TChange extends PositionChangeLike>(changes: TChange[]): TChange[] {\n\t\t\tconst dims = changes.find(\n\t\t\t\t(c) => c.type === 'dimensions' && typeof c.id === 'string' && !!c.dimensions && typeof c.resizing === 'boolean'\n\t\t\t);\n\t\t\tif (!dims) return changes;\n\t\t\tconst id = dims.id as string;\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tconst node = lookup.get(id);\n\t\t\tconst isResizeChange = (c: TChange) => c.id === id && (c.type === 'dimensions' || c.type === 'position');\n\t\t\tif (node && isLocked(node)) {\n\t\t\t\tif (dims.resizing === false) controller.endResize();\n\t\t\t\treturn changes.filter((c) => !isResizeChange(c));\n\t\t\t}\n\t\t\tif (dims.resizing === false) {\n\t\t\t\tconst last = lastResized?.updates[0];\n\t\t\t\tlastResized = null; // applied to the change below, not by endResize\n\t\t\t\tcontroller.endResize();\n\t\t\t\tif (!last || last.id !== id) return changes;\n\t\t\t\treturn changes.map((c) =>\n\t\t\t\t\tc === dims ? { ...c, dimensions: { width: last.width!, height: last.height! } } : c\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (!node || (options.resizeSnapping ?? options.snapping) === false) return changes;\n\t\t\tif (resizeId !== id) controller.beginResize(id);\n\t\t\tif (!resizeStart) return changes;\n\t\t\tconst posChange = changes.find((c) => c.id === id && c.type === 'position' && !!c.position);\n\t\t\tconst parent = node.parentId ? lookup.get(node.parentId) : undefined;\n\t\t\tconst origin = parent ? getResizingNodeRect(parent, lookup, rect()) : { x: 0, y: 0 };\n\t\t\tconst position = posChange?.position ?? node.position;\n\t\t\tconst proposed: Rect = {\n\t\t\t\tx: position.x + origin.x,\n\t\t\t\ty: position.y + origin.y,\n\t\t\t\twidth: dims.dimensions.width,\n\t\t\t\theight: dims.dimensions.height\n\t\t\t};\n\t\t\tconst handle = inferResizeHandle(resizeStart, proposed);\n\t\t\tif (!handle) {\n\t\t\t\tsetGuides([]);\n\t\t\t\treturn changes;\n\t\t\t}\n\t\t\tconst result = snapNodeResize(nodes, id, proposed, handle, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\t...options.resize,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: resizeIndex!\n\t\t\t});\n\t\t\tsetGuides(result.guides);\n\t\t\tconst update = result.updates[0];\n\t\t\tlastResized = update ? result : null;\n\t\t\tif (!update) return changes;\n\t\t\tconst next = changes.map((c) => {\n\t\t\t\tif (c === dims) return { ...c, dimensions: { width: update.width!, height: update.height! } };\n\t\t\t\tif (c === posChange) return { ...c, position: update.position! };\n\t\t\t\treturn c;\n\t\t\t});\n\t\t\tif (!posChange && (update.position!.x !== node.position.x || update.position!.y !== node.position.y)) {\n\t\t\t\tnext.push({ id, type: 'position', position: update.position! } as unknown as TChange);\n\t\t\t}\n\t\t\treturn next;\n\t\t},\n\n\t\t/**\n\t\t * Clears guides and the cached snap targets. Call it from the flow's drag-stop\n\t\t * handler: flow libraries write the raw pointer position when a drag ends, so the\n\t\t * last snapped positions from `drag` are written back here.\n\t\t */\n\t\tendDrag() {\n\t\t\tconst snapped = lastSnapped;\n\t\t\tdragIndex = null;\n\t\t\tdragKey = '';\n\t\t\tlastSnapped = new Map();\n\t\t\tsetGuides([]);\n\t\t\tif (snapped.size === 0) return;\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst drift = nodes.some((n) => {\n\t\t\t\tconst p = snapped.get(n.id);\n\t\t\t\treturn !!p && (n.position.x !== p.x || n.position.y !== p.y);\n\t\t\t});\n\t\t\tif (drift) {\n\t\t\t\toptions.setNodes(applyNodeUpdates(nodes, [...snapped].map(([id, position]) => ({ id, position }))));\n\t\t\t}\n\t\t}\n\t};\n\treturn controller;\n}\n\nexport type CanvasArrangeController<TNode extends CanvasNode = CanvasNode> = ReturnType<\n\ttypeof createCanvasArrangeController<TNode>\n>;\n","/**\n * Distance measuring between bounding boxes (Alt-hover red lines with value labels).\n *\n * Portions ported from figcraft-main/crates/engine/src/distance.rs (MIT OR Apache-2.0,\n * © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n */\nimport { boundsOf } from './layout.js';\nimport { createNodeLookup, getNodeRect, type NodeRectOptions } from './arrange.js';\nimport type { GuideLine } from './guides.js';\nimport type { CanvasNode, Rect } from './types.js';\n\n/** Lines shorter than this (flow units) are not drawn. */\nconst EPS = 1e-6;\n\n/**\n * A measuring line in flow coordinates. `measure` lines carry a `label` (the distance);\n * `extension` lines are dashed, unlabelled lines that carry a gap over to the measured box\n * when the gap line passes beside it.\n */\nexport interface MeasureGuide extends GuideLine {\n\tkind: 'measure' | 'extension';\n\t/** The distance as shown on the label (`measure` lines only). */\n\tlabel?: string;\n}\n\n/** A distance as shown on a label: absolute value, up to two decimals, no trailing zeros. */\nexport function measureLabel(distance: number): string {\n\tconst v = Math.round(Math.abs(distance) * 100) / 100;\n\treturn Object.is(v, -0) ? '0' : String(v);\n}\n\ninterface Box {\n\tx0: number;\n\ty0: number;\n\tx1: number;\n\ty1: number;\n}\n\nconst toBox = (r: Rect): Box => ({ x0: r.x, y0: r.y, x1: r.x + r.width, y1: r.y + r.height });\nconst finiteBox = (b: Box) => [b.x0, b.y0, b.x1, b.y1].every(Number.isFinite);\n\nfunction line(kind: 'measure' | 'extension', x1: number, y1: number, x2: number, y2: number): MeasureGuide {\n\treturn { orientation: x1 === x2 ? 'vertical' : 'horizontal', kind, x1, y1, x2, y2 };\n}\n\n/** A labelled distance line, unless it has no length. */\nfunction measured(out: MeasureGuide[], x1: number, y1: number, x2: number, y2: number) {\n\tconst d = Math.hypot(x2 - x1, y2 - y1);\n\tif (d > EPS && Number.isFinite(d)) out.push({ ...line('measure', x1, y1, x2, y2), label: measureLabel(d) });\n}\n\n/** A dashed extension line (no label), unless it has no length. */\nfunction extension(out: MeasureGuide[], x1: number, y1: number, x2: number, y2: number) {\n\tif (Math.hypot(x2 - x1, y2 - y1) > EPS) out.push(line('extension', x1, y1, x2, y2));\n}\n\nfunction contains(outer: Box, inner: Box): boolean {\n\treturn outer.x0 <= inner.x0 + EPS && outer.y0 <= inner.y0 + EPS && outer.x1 >= inner.x1 - EPS && outer.y1 >= inner.y1 - EPS;\n}\n\n/** The middle of the overlap of two spans, or `null` when they do not overlap. */\nfunction overlapMid(a0: number, a1: number, b0: number, b1: number): number | null {\n\tconst lo = Math.max(a0, b0);\n\tconst hi = Math.min(a1, b1);\n\treturn lo <= hi ? (lo + hi) / 2 : null;\n}\n\n/** From `inner` to each edge of `outer`, along `inner`'s centre lines. */\nfunction inside(out: MeasureGuide[], inner: Box, outer: Box) {\n\tconst cx = (inner.x0 + inner.x1) / 2;\n\tconst cy = (inner.y0 + inner.y1) / 2;\n\tmeasured(out, outer.x0, cy, inner.x0, cy);\n\tmeasured(out, inner.x1, cy, outer.x1, cy);\n\tmeasured(out, cx, outer.y0, cx, inner.y0);\n\tmeasured(out, cx, inner.y1, cx, outer.y1);\n}\n\n/**\n * Distance lines from box `a` (the selection) to box `b` (the hovered shape):\n *\n * - **One inside the other**: from the inner box to each edge of the outer one, along the\n * inner box's centre lines (the same lines either way round).\n * - **Overlapping**: between the matching edges (left to left, right to right, ...).\n * - **Apart**: the horizontal and/or vertical gap, across the shared rows / columns, or from\n * the middle of `a` with a dashed `extension` line to `b` when the gap line passes beside it.\n * Touching boxes have no gap to draw.\n */\nexport function measureBetween(a: Rect, b: Rect): MeasureGuide[] {\n\tconst out: MeasureGuide[] = [];\n\tconst A = toBox(a);\n\tconst B = toBox(b);\n\tif (!finiteBox(A) || !finiteBox(B)) return out;\n\tif (contains(B, A)) {\n\t\tinside(out, A, B);\n\t\treturn out;\n\t}\n\tif (contains(A, B)) {\n\t\tinside(out, B, A);\n\t\treturn out;\n\t}\n\tconst ox = overlapMid(A.x0, A.x1, B.x0, B.x1);\n\tconst oy = overlapMid(A.y0, A.y1, B.y0, B.y1);\n\tconst overlapping = A.x0 < B.x1 && B.x0 < A.x1 && A.y0 < B.y1 && B.y0 < A.y1;\n\tif (overlapping && ox !== null && oy !== null) {\n\t\tmeasured(out, A.x0, oy, B.x0, oy);\n\t\tmeasured(out, A.x1, oy, B.x1, oy);\n\t\tmeasured(out, ox, A.y0, ox, B.y0);\n\t\tmeasured(out, ox, A.y1, ox, B.y1);\n\t\treturn out;\n\t}\n\tconst cx = (A.x0 + A.x1) / 2;\n\tconst cy = (A.y0 + A.y1) / 2;\n\tconst gapX = B.x0 >= A.x1 ? [A.x1, B.x0] : B.x1 <= A.x0 ? [A.x0, B.x1] : null;\n\tif (gapX) {\n\t\tconst [from, to] = gapX;\n\t\tconst y = oy ?? cy;\n\t\tmeasured(out, from, y, to, y);\n\t\tif (oy === null) extension(out, to, y, to, y < B.y0 ? B.y0 : B.y1);\n\t}\n\tconst gapY = B.y0 >= A.y1 ? [A.y1, B.y0] : B.y1 <= A.y0 ? [A.y0, B.y1] : null;\n\tif (gapY) {\n\t\tconst [from, to] = gapY;\n\t\tconst x = ox ?? cx;\n\t\tmeasured(out, x, from, x, to);\n\t\tif (ox === null) extension(out, x, to, x < B.x0 ? B.x0 : B.x1, to);\n\t}\n\treturn out;\n}\n\n/**\n * Distance lines from box `a` to an infinite guide line (`vertical` at x = `at`, otherwise\n * horizontal at y = `at`): to the nearer side, or to both sides when the line crosses the box.\n */\nexport function measureToLine(a: Rect, orientation: 'vertical' | 'horizontal', at: number): MeasureGuide[] {\n\tconst out: MeasureGuide[] = [];\n\tconst A = toBox(a);\n\tif (!finiteBox(A) || !Number.isFinite(at)) return out;\n\tconst vertical = orientation === 'vertical';\n\tconst cx = (A.x0 + A.x1) / 2;\n\tconst cy = (A.y0 + A.y1) / 2;\n\tconst [lo, hi] = vertical ? [A.x0, A.x1] : [A.y0, A.y1];\n\tconst seg = (from: number, to: number) =>\n\t\tvertical ? measured(out, from, cy, to, cy) : measured(out, cx, from, cx, to);\n\tif (at >= hi) seg(hi, at);\n\telse if (at <= lo) seg(at, lo);\n\telse {\n\t\tseg(lo, at);\n\t\tseg(at, hi);\n\t}\n\treturn out;\n}\n\nexport interface MeasureSelectionResult {\n\t/** Measuring lines in flow coordinates. */\n\tguides: MeasureGuide[];\n\t/** The box measured to (to outline), or `null` when nothing is measured. */\n\ttarget: Rect | null;\n\t/** The selection's bounding box, or `null` without a selection. */\n\tselection: Rect | null;\n}\n\nexport interface MeasureSelectionOptions extends NodeRectOptions {}\n\n/**\n * Alt-hover measuring on canvas nodes: from the bounding box of `selectedIds` (absolute,\n * union of several nodes) to the hovered node. Hovering the selection itself, or nothing\n * (`hoveredId` null), measures to the selection's parent (`parentId`) when it has one;\n * a top-level selection then measures nothing. Hidden nodes are ignored.\n */\nexport function measureSelection(\n\tnodes: CanvasNode[],\n\tselectedIds: Iterable<string>,\n\thoveredId: string | null | undefined,\n\toptions: MeasureSelectionOptions = {}\n): MeasureSelectionResult {\n\tconst none = (selection: Rect | null = null): MeasureSelectionResult => ({ guides: [], target: null, selection });\n\tconst lookup = createNodeLookup(nodes);\n\tconst selected = [...new Set(selectedIds)]\n\t\t.map((id) => lookup.get(id))\n\t\t.filter((n): n is CanvasNode => !!n && n.hidden !== true);\n\tif (selected.length === 0) return none();\n\tconst selection = boundsOf(selected.map((n) => getNodeRect(n, lookup, options)));\n\tif (!selection) return none();\n\tconst selectedSet = new Set(selected.map((n) => n.id));\n\n\tlet targetId = hoveredId ?? null;\n\tif (targetId === null || selectedSet.has(targetId)) {\n\t\tconst parentId = selected[0].parentId;\n\t\tconst sameParent = selected.every((n) => n.parentId === parentId);\n\t\ttargetId = parentId && sameParent && !selectedSet.has(parentId) ? parentId : null;\n\t}\n\tconst target = targetId !== null ? lookup.get(targetId) : undefined;\n\tif (!target || target.hidden === true) return none(selection);\n\tconst rect = getNodeRect(target, lookup, options);\n\treturn { guides: measureBetween(selection, rect), target: rect, selection };\n}\n\n/**\n * Whether the measuring modifier (Alt / Option) is held after a keyboard or pointer event.\n * `keyup` of Alt releases it; `keydown` of Alt or any event with `altKey` holds it.\n */\nexport function isMeasureModifierHeld(\n\tevent: { type?: string; key?: string; altKey?: boolean } | null | undefined\n): boolean {\n\tif (!event) return false;\n\tif (event.type === 'keyup' && event.key === 'Alt') return false;\n\treturn event.altKey === true || (event.type === 'keydown' && event.key === 'Alt');\n}\n","import { applyNodeUpdates, createNodeLookup, getNodeRect, getNodeSize, invertNodeUpdates, type CanvasNodeUpdate } from './arrange.js';\nimport type { ArrangeHistoryTarget, PositionChangeLike } from './arrangeController.js';\nimport {\n\tapplyConstraints,\n\tgetAutoLayoutInsertTarget,\n\tgetLayoutParent,\n\tisAutoLayoutFrame,\n\tlayoutAll,\n\tlayoutUpMany,\n\tmoveLayoutChild,\n\ttype AutoLayoutInsertTarget,\n\ttype AutoLayoutOptions\n} from './autoLayout.js';\nimport type { GuideLine } from './guides.js';\nimport type { CanvasNode, XYPosition } from './types.js';\n\nexport interface CanvasAutoLayoutControllerOptions<TNode extends CanvasNode = CanvasNode> extends AutoLayoutOptions {\n\tgetNodes: () => TNode[];\n\tsetNodes: (nodes: TNode[]) => void;\n\t/** When set, controller actions (add / remove / resize / reorder / apply) are one undoable step each. */\n\thistory?: ArrangeHistoryTarget;\n\t/** Turns automatic relayout off (explicit `applyAutoLayout` still works). Default `true`. */\n\tenabled?: boolean;\n\t/** Reorder flow children by dragging them inside their frame (default `true`). */\n\tdragReorder?: boolean;\n\t/** Called when the drop marker of a reorder drag changes (empty when none). */\n\tonGuidesChange?: (guides: GuideLine[]) => void;\n}\n\n/**\n * Merges the updates of several layout passes (later ones win per field).\n */\nexport function mergeNodeUpdates(...lists: CanvasNodeUpdate[][]): CanvasNodeUpdate[] {\n\tconst byId = new Map<string, CanvasNodeUpdate>();\n\tfor (const list of lists) for (const u of list) byId.set(u.id, { ...byId.get(u.id), ...u });\n\treturn [...byId.values()];\n}\n\n/**\n * Updates that re-run auto layout around `ids` after a change: each id's own layout (if a\n * frame) and its auto layout ancestors, plus the frames that lost removed children\n * (`parentIds`). Pure helper used by the controllers.\n */\nexport function relayoutUpdates(nodes: CanvasNode[], ids: Iterable<string>, options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\treturn layoutUpMany(nodes, ids, options);\n}\n\n/**\n * Framework-agnostic auto layout controller shared by the React hook and the Svelte rune.\n * Applies auto layout to frames (`data.layout`) after nodes inside them are added, removed,\n * resized or reordered, either through its own (undoable) actions or by watching the flow's\n * node changes (`noteChanges` + `flush`), and reorders flow children dragged within a frame.\n */\nexport function createCanvasAutoLayoutController<TNode extends CanvasNode = CanvasNode>(\n\toptions: CanvasAutoLayoutControllerOptions<TNode>\n) {\n\tconst layoutOptions = (): AutoLayoutOptions => ({ measure: options.measure, rectOptions: options.rectOptions });\n\tconst enabled = () => options.enabled !== false;\n\n\t// Changes noted from the flow, applied by `flush`.\n\tconst pendingIds = new Set<string>();\n\tconst pendingResizes = new Map<string, { width: number; height: number }>();\n\tconst resizeStarts = new Map<string, { width: number; height: number }>();\n\tlet pendingAll = false;\n\t// Reorder drags: start position of each dragged flow child, and drops to commit.\n\tconst dragStarts = new Map<string, XYPosition>();\n\tconst pendingDrops = new Set<string>();\n\tlet guides: GuideLine[] = [];\n\n\tconst setGuides = (next: GuideLine[]) => {\n\t\tif (next.length === 0 && guides.length === 0) return;\n\t\tguides = next;\n\t\toptions.onGuidesChange?.(guides);\n\t};\n\n\t/**\n\t * Runs a structural change plus the relayout it causes as one (optionally undoable) step.\n\t * `forward` / `backward` change the array; `ids` are relayout around after `forward`.\n\t */\n\tfunction run(\n\t\tname: string,\n\t\tforward: (nodes: TNode[]) => TNode[],\n\t\tbackward: (nodes: TNode[]) => TNode[],\n\t\tids: (after: TNode[]) => Iterable<string>,\n\t\textra: CanvasNodeUpdate[] = [],\n\t\trestore?: { id: string; position: XYPosition }\n\t) {\n\t\tconst before = options.getNodes();\n\t\tconst structural = forward(before);\n\t\tconst withExtra = applyNodeUpdates(structural, extra);\n\t\tconst layout = enabled() || name === 'Auto Layout' ? relayoutUpdates(withExtra, ids(withExtra), layoutOptions()) : [];\n\t\tconst updates = mergeNodeUpdates(extra, layout);\n\t\tif (structural === before && updates.length === 0) return updates;\n\t\tlet inverse = invertNodeUpdates(structural, updates);\n\t\tif (restore) {\n\t\t\t// Undo puts a dragged node back where the drag began (the flow wrote the drag outside history).\n\t\t\tinverse = mergeNodeUpdates(inverse, [{ id: restore.id, position: { ...restore.position } }]);\n\t\t}\n\t\tconst execute = () => options.setNodes(applyNodeUpdates(forward(options.getNodes()), updates));\n\t\tconst undo = () => options.setNodes(backward(applyNodeUpdates(options.getNodes(), inverse)));\n\t\tif (options.history) options.history.execute({ name, execute, undo });\n\t\telse execute();\n\t\treturn updates;\n\t}\n\n\tconst identity = (nodes: TNode[]) => nodes;\n\n\tconst controller = {\n\t\t/** Drop marker of the active reorder drag (flow coordinates); empty otherwise. */\n\t\tget guides() {\n\t\t\treturn guides;\n\t\t},\n\n\t\t/**\n\t\t * Lays out frame `frameId` and its auto layout ancestors (default: every frame) as one\n\t\t * (optionally undoable) step. Returns the updates.\n\t\t */\n\t\tapplyAutoLayout(frameId?: string): CanvasNodeUpdate[] {\n\t\t\tif (frameId === undefined) {\n\t\t\t\tconst updates = layoutAll(options.getNodes(), layoutOptions());\n\t\t\t\treturn run('Auto Layout', identity, identity, () => [], updates);\n\t\t\t}\n\t\t\treturn run('Auto Layout', identity, identity, () => [frameId]);\n\t\t},\n\n\t\t/** Updates that relayout around `ids` on the current nodes, without applying them. */\n\t\tlayoutUpdates(ids: Iterable<string>): CanvasNodeUpdate[] {\n\t\t\treturn relayoutUpdates(options.getNodes(), ids, layoutOptions());\n\t\t},\n\n\t\t/**\n\t\t * Applies the relayout around `ids` (frames, or nodes inside frames) directly, not\n\t\t * recorded in history: auto layout is derived state, re-run after any change.\n\t\t */\n\t\trelayout(ids: Iterable<string>): CanvasNodeUpdate[] {\n\t\t\tconst updates = relayoutUpdates(options.getNodes(), ids, layoutOptions());\n\t\t\tif (updates.length > 0) options.setNodes(applyNodeUpdates(options.getNodes(), updates));\n\t\t\treturn updates;\n\t\t},\n\n\t\t/**\n\t\t * Adds `node` (into the frame named by its `parentId`, at flow `index` when given) and\n\t\t * lays out its frame, as one step.\n\t\t */\n\t\taddNode(node: TNode, addOptions: { index?: number } = {}): CanvasNodeUpdate[] {\n\t\t\tconst forward = (nodes: TNode[]) => {\n\t\t\t\tif (nodes.some((n) => n.id === node.id)) return nodes;\n\t\t\t\tlet next = [...nodes, node];\n\t\t\t\tif (addOptions.index !== undefined && node.parentId) next = moveLayoutChild(next, node.id, addOptions.index);\n\t\t\t\treturn next;\n\t\t\t};\n\t\t\tconst backward = (nodes: TNode[]) => nodes.filter((n) => n.id !== node.id);\n\t\t\treturn run('Add Node', forward, backward, () => [node.id]);\n\t\t},\n\n\t\t/** Removes nodes (and their descendants) and lays out the frames they leave, as one step. */\n\t\tremoveNodes(ids: Iterable<string>): CanvasNodeUpdate[] {\n\t\t\tconst before = options.getNodes();\n\t\t\tconst remove = new Set(ids);\n\t\t\t// Descendants go too (xyflow cannot render orphans).\n\t\t\tlet grew = true;\n\t\t\twhile (grew) {\n\t\t\t\tgrew = false;\n\t\t\t\tfor (const n of before) {\n\t\t\t\t\tif (n.parentId && remove.has(n.parentId) && !remove.has(n.id)) {\n\t\t\t\t\t\tremove.add(n.id);\n\t\t\t\t\t\tgrew = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst removed = before.map((n, i) => [i, n] as const).filter(([, n]) => remove.has(n.id));\n\t\t\tif (removed.length === 0) return [];\n\t\t\tconst parents = [...new Set(removed.map(([, n]) => n.parentId).filter((p): p is string => !!p && !remove.has(p)))];\n\t\t\tconst forward = (nodes: TNode[]) => nodes.filter((n) => !remove.has(n.id));\n\t\t\tconst backward = (nodes: TNode[]) => {\n\t\t\t\tconst next = [...nodes];\n\t\t\t\tfor (const [i, n] of removed) next.splice(Math.min(i, next.length), 0, n);\n\t\t\t\treturn next;\n\t\t\t};\n\t\t\treturn run('Delete', forward, backward, () => parents);\n\t\t},\n\n\t\t/**\n\t\t * Resizes node `id` (its children follow their constraints) and lays out around it,\n\t\t * as one step.\n\t\t */\n\t\tresizeNode(id: string, size: { width?: number; height?: number }): CanvasNodeUpdate[] {\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst node = nodes.find((n) => n.id === id);\n\t\t\tif (!node) return [];\n\t\t\tconst old = getNodeSize(node, options.rectOptions);\n\t\t\tconst update: CanvasNodeUpdate = { id };\n\t\t\tif (size.width !== undefined) update.width = size.width;\n\t\t\tif (size.height !== undefined) update.height = size.height;\n\t\t\tconst resized = applyNodeUpdates(nodes, [update]);\n\t\t\tconst constrained = mergeNodeUpdates([update], applyConstraints(resized, id, old, layoutOptions()));\n\t\t\treturn run('Resize', identity, identity, () => [id], constrained);\n\t\t},\n\n\t\t/** Moves flow child `childId` to flow index `index` in its frame and lays it out, as one step. */\n\t\tmoveChild(childId: string, index: number): CanvasNodeUpdate[] {\n\t\t\tconst before = options.getNodes();\n\t\t\tconst order = before.map((n) => n.id);\n\t\t\tconst forward = (nodes: TNode[]) => moveLayoutChild(nodes, childId, index);\n\t\t\tconst backward = (nodes: TNode[]) => restoreOrder(nodes, order);\n\t\t\tif (forward(before) === before) return [];\n\t\t\treturn run('Reorder', forward, backward, () => [childId]);\n\t\t},\n\n\t\t/** Where flow child `id` would drop if released at `point` (default: its centre). */\n\t\tgetInsertTarget(id: string, point?: XYPosition): AutoLayoutInsertTarget | undefined {\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst frame = getLayoutParent(nodes, id);\n\t\t\tif (!frame) return undefined;\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tconst rect = getNodeRect(lookup.get(id)!, lookup, options.rectOptions);\n\t\t\tconst at = point ?? { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };\n\t\t\treturn getAutoLayoutInsertTarget(nodes, frame.id, at, { ...layoutOptions(), excludeId: id });\n\t\t},\n\n\t\t/** Records the start position of a reorder drag. Optional: `dragReorder` records it lazily. */\n\t\tbeginReorderDrag(id: string) {\n\t\t\tconst node = options.getNodes().find((n) => n.id === id);\n\t\t\tif (node && getLayoutParent(options.getNodes(), id)) dragStarts.set(id, { ...node.position });\n\t\t},\n\n\t\t/**\n\t\t * While flow child `id` is dragged (as currently positioned in `getNodes()`): updates the\n\t\t * drop marker. Returns the insert target.\n\t\t */\n\t\tdragReorder(id: string, point?: XYPosition): AutoLayoutInsertTarget | undefined {\n\t\t\tif (options.dragReorder === false) return undefined;\n\t\t\tif (!dragStarts.has(id)) controller.beginReorderDrag(id);\n\t\t\tconst target = controller.getInsertTarget(id, point);\n\t\t\tsetGuides(target ? [target.guide] : []);\n\t\t\treturn target;\n\t\t},\n\n\t\t/**\n\t\t * Ends a reorder drag: moves `id` to the slot under `point` (default: its centre) and lays\n\t\t * the frame out, as one undoable step whose undo puts the node back where the drag began.\n\t\t */\n\t\tendReorderDrag(id: string, point?: XYPosition): CanvasNodeUpdate[] {\n\t\t\tsetGuides([]);\n\t\t\tconst start = dragStarts.get(id);\n\t\t\tdragStarts.delete(id);\n\t\t\tif (options.dragReorder === false || !getLayoutParent(options.getNodes(), id)) return [];\n\t\t\tconst target = controller.getInsertTarget(id, point);\n\t\t\tif (!target) return [];\n\t\t\tconst before = options.getNodes();\n\t\t\tconst order = before.map((n) => n.id);\n\t\t\tconst forward = (nodes: TNode[]) => moveLayoutChild(nodes, id, target.index);\n\t\t\tconst backward = (nodes: TNode[]) => restoreOrder(nodes, order);\n\t\t\treturn run('Reorder', forward, backward, () => [id], [], start && { id, position: start });\n\t\t},\n\n\t\t/**\n\t\t * Notes a batch of React Flow / SvelteFlow style node changes (before or after they are\n\t\t * applied) so `flush` can relayout: dimension changes (measured sizes, resize ends),\n\t\t * added and removed nodes, and position changes of flow children (reorder drags).\n\t\t * Returns the changes unchanged.\n\t\t */\n\t\tnoteChanges<TChange extends PositionChangeLike>(changes: TChange[]): TChange[] {\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tfor (const c of changes) {\n\t\t\t\tconst id = c.id ?? c.item?.id;\n\t\t\t\tif (c.type === 'add' && c.item) {\n\t\t\t\t\tpendingIds.add(c.item.id);\n\t\t\t\t} else if (c.type === 'remove' && id) {\n\t\t\t\t\tconst parent = lookup.get(id)?.parentId;\n\t\t\t\t\tif (parent) pendingIds.add(parent);\n\t\t\t\t\telse pendingAll = true;\n\t\t\t\t} else if (c.type === 'replace' && id) {\n\t\t\t\t\tpendingIds.add(id);\n\t\t\t\t} else if (c.type === 'dimensions' && id) {\n\t\t\t\t\tconst node = lookup.get(id);\n\t\t\t\t\tif (c.resizing === true) {\n\t\t\t\t\t\tif (node && !resizeStarts.has(id)) resizeStarts.set(id, getNodeSize(node, options.rectOptions));\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tconst start = resizeStarts.get(id);\n\t\t\t\t\tresizeStarts.delete(id);\n\t\t\t\t\tif (start) pendingResizes.set(id, start);\n\t\t\t\t\tpendingIds.add(id);\n\t\t\t\t} else if (c.type === 'position' && id && options.dragReorder !== false) {\n\t\t\t\t\tif (!getLayoutParent(nodes, id)) continue;\n\t\t\t\t\tif (c.dragging === true) {\n\t\t\t\t\t\tif (!dragStarts.has(id)) {\n\t\t\t\t\t\t\tconst node = lookup.get(id);\n\t\t\t\t\t\t\tif (node) dragStarts.set(id, { ...node.position });\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (c.dragging === false || dragStarts.has(id)) {\n\t\t\t\t\t\tpendingDrops.add(id);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn changes;\n\t\t},\n\n\t\t/**\n\t\t * Notes nodes that were already removed (e.g. SvelteFlow's `ondelete`), so `flush`\n\t\t * relayouts the frames they left. The nodes must still carry their `parentId`.\n\t\t */\n\t\tnoteRemoved(removed: Iterable<{ id: string; parentId?: string }>) {\n\t\t\tconst gone = new Set<string>();\n\t\t\tconst list = [...removed];\n\t\t\tfor (const n of list) gone.add(n.id);\n\t\t\tfor (const n of list) if (n.parentId && !gone.has(n.parentId)) pendingIds.add(n.parentId);\n\t\t},\n\n\t\t/** Whether `noteChanges` left work for `flush`. */\n\t\tget hasPending() {\n\t\t\treturn pendingAll || pendingIds.size > 0 || pendingResizes.size > 0 || pendingDrops.size > 0 || dragStarts.size > 0;\n\t\t},\n\n\t\t/**\n\t\t * Applies the work noted by `noteChanges` to the current nodes: drop markers while\n\t\t * dragging, committed reorders on drop (undoable), and a relayout (not recorded) for\n\t\t * size, add and remove changes. Call it once the changes are applied.\n\t\t */\n\t\tflush(): CanvasNodeUpdate[] {\n\t\t\tlet applied: CanvasNodeUpdate[] = [];\n\t\t\tfor (const id of pendingDrops) applied = mergeNodeUpdates(applied, controller.endReorderDrag(id));\n\t\t\tpendingDrops.clear();\n\t\t\tfor (const id of dragStarts.keys()) controller.dragReorder(id);\n\t\t\tif (!enabled()) {\n\t\t\t\tpendingIds.clear();\n\t\t\t\tpendingResizes.clear();\n\t\t\t\tpendingAll = false;\n\t\t\t\treturn applied;\n\t\t\t}\n\t\t\tlet nodes = options.getNodes();\n\t\t\tlet updates: CanvasNodeUpdate[] = [];\n\t\t\tfor (const [id, previous] of pendingResizes) {\n\t\t\t\tif (!nodes.some((n) => n.id === id)) continue;\n\t\t\t\tconst constrained = applyConstraints(nodes, id, previous, layoutOptions());\n\t\t\t\tnodes = applyNodeUpdates(nodes, constrained);\n\t\t\t\tupdates = mergeNodeUpdates(updates, constrained);\n\t\t\t}\n\t\t\tconst layout = pendingAll\n\t\t\t\t? layoutAll(nodes, layoutOptions())\n\t\t\t\t: relayoutUpdates(nodes, [...pendingIds].filter((id) => nodes.some((n) => n.id === id)), layoutOptions());\n\t\t\tupdates = mergeNodeUpdates(updates, layout);\n\t\t\tpendingIds.clear();\n\t\t\tpendingResizes.clear();\n\t\t\tpendingAll = false;\n\t\t\tif (updates.length > 0) options.setNodes(applyNodeUpdates(options.getNodes(), updates));\n\t\t\treturn mergeNodeUpdates(applied, updates);\n\t\t},\n\n\t\t/** Whether `id` is an auto layout frame. */\n\t\tisFrame: (id: string) => isAutoLayoutFrame(options.getNodes().find((n) => n.id === id))\n\t};\n\n\treturn controller;\n}\n\n/** Reorders `nodes` to follow `order` (ids); ids not in `order` keep their relative place at the end. */\nfunction restoreOrder<TNode extends CanvasNode>(nodes: TNode[], order: string[]): TNode[] {\n\tconst rank = new Map(order.map((id, i) => [id, i]));\n\tconst indexed = nodes.map((n, i) => ({ n, r: rank.get(n.id) ?? order.length + i }));\n\tindexed.sort((a, b) => a.r - b.r);\n\treturn indexed.every((e, i) => e.n === nodes[i]) ? nodes : indexed.map((e) => e.n);\n}\n\nexport type CanvasAutoLayoutController<TNode extends CanvasNode = CanvasNode> = ReturnType<\n\ttypeof createCanvasAutoLayoutController<TNode>\n>;\n","// Portions ported from figcraft/crates/doc/src/lib.rs and figcraft/crates/engine/src/cmd/variables.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport { AUTO_LAYOUT_DATA_KEY, getAutoLayout, resolvePadding } from './autoLayout.js';\nimport { isShapeDescription } from './shapeGeometry.js';\nimport type { CanvasNode, CanvasNodeStyle } from './types.js';\nimport {\n\tTokenError,\n\tgetTokenCollection,\n\tgetTokenVariable,\n\tisTokenInScope,\n\tresolveToken,\n\ttype TokenLiteral,\n\ttype TokenModeContext,\n\ttype TokenResolution,\n\ttype TokenScope,\n\ttype TokenState,\n\ttype TokenType,\n\ttype TokenVariable\n} from '../tokens/index.js';\n\n/**\n * Design-token bindings on canvas nodes.\n *\n * - `node.data.tokenBindings` maps a property key (`fill`, `opacity`, `gap`, …) to a variable id.\n * - `node.data.tokenModes` maps a collection id to a mode id: an explicit mode for the node and\n * its descendants (via `parentId`); the nearest ancestor wins, then the root `modes` option,\n * then the collection's default mode.\n *\n * Bound properties are *materialised*: `resolveCanvasTokens` writes the resolved values into\n * the ordinary node fields (`data.shape.fill`, `style.backgroundColor`, `data.layout.gap`, …), so\n * renderers, auto layout and exports need no token awareness, and a node whose variable is\n * gone keeps its last value. Both keys live in `data`, so they survive copy / paste,\n * duplication and serialisation unchanged.\n */\n\n/** Data key holding a node's property → variable id bindings. */\nexport const TOKEN_BINDINGS_DATA_KEY = 'tokenBindings';\n/** Data key holding a node's explicit collection → mode choices. */\nexport const TOKEN_MODES_DATA_KEY = 'tokenModes';\n\n/** A node property a variable can drive. */\nexport interface CanvasTokenProperty {\n\tkey: string;\n\tlabel: string;\n\t/** The variable type it takes. */\n\ttype: TokenType;\n\t/** The scope pickers filter by (variables scoped elsewhere are not offered). */\n\tscope?: TokenScope | ((node: CanvasNode) => TokenScope | undefined);\n\tappliesTo(node: CanvasNode): boolean;\n\tread(node: CanvasNode): TokenLiteral | undefined;\n\t/** Returns the node with the value written, or the same node when nothing changes. */\n\twrite(node: CanvasNode, value: TokenLiteral): CanvasNode;\n}\n\n// ---------------------------------------------------------------------------------------------\n// Style helpers (CSS string for SvelteFlow, object for React Flow)\n\nconst kebab = (prop: string) => prop.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);\n\n/** Splits a style string at top-level `;` (not inside quotes or parentheses, e.g. data URLs). */\nfunction parseDeclarations(style: string): [string, string][] {\n\tconst parts: string[] = [];\n\tlet depth = 0;\n\tlet quote = '';\n\tlet start = 0;\n\tfor (let i = 0; i < style.length; i++) {\n\t\tconst ch = style[i];\n\t\tif (quote) {\n\t\t\tif (ch === '\\\\') i++;\n\t\t\telse if (ch === quote) quote = '';\n\t\t} else if (ch === '\"' || ch === \"'\") quote = ch;\n\t\telse if (ch === '(') depth++;\n\t\telse if (ch === ')') depth = Math.max(0, depth - 1);\n\t\telse if (ch === ';' && depth === 0) {\n\t\t\tparts.push(style.slice(start, i));\n\t\t\tstart = i + 1;\n\t\t}\n\t}\n\tparts.push(style.slice(start));\n\tconst out: [string, string][] = [];\n\tfor (const part of parts) {\n\t\tconst colon = part.indexOf(':');\n\t\tif (colon < 0) continue;\n\t\tconst name = part.slice(0, colon).trim();\n\t\tif (name) out.push([name, part.slice(colon + 1).trim()]);\n\t}\n\treturn out;\n}\n\n/** The top-level `name: value` declarations of a style string (names as written, in order). */\nexport function parseNodeStyleDeclarations(style: string): [string, string][] {\n\treturn parseDeclarations(style);\n}\n\n/** A style property (camelCase name) from a style string or object. */\nexport function readNodeStyleProperty(style: CanvasNodeStyle | undefined | null, prop: string): string | number | undefined {\n\tif (!style) return undefined;\n\tif (typeof style === 'object') return style[prop];\n\tconst name = kebab(prop);\n\tlet found: string | undefined;\n\tfor (const [key, value] of parseDeclarations(style)) if (key.toLowerCase() === name) found = value;\n\treturn found;\n}\n\n/**\n * Returns `style` with one property set (or removed with `undefined`), keeping its\n * representation. Numbers become `px` in style strings unless `unitless`.\n */\nexport function writeNodeStyleProperty(\n\tstyle: CanvasNodeStyle | undefined | null,\n\tprop: string,\n\tvalue: string | number | undefined,\n\tunitless = false\n): CanvasNodeStyle | undefined {\n\tif (typeof style === 'string') {\n\t\tconst name = kebab(prop);\n\t\tconst text = value === undefined ? undefined : typeof value === 'number' && !unitless ? `${value}px` : String(value);\n\t\t// Replace in place (keeping declaration order), drop duplicates, or append.\n\t\tconst decls: [string, string][] = [];\n\t\tlet placed = text === undefined;\n\t\tfor (const [key, current] of parseDeclarations(style)) {\n\t\t\tif (key.toLowerCase() !== name) decls.push([key, current]);\n\t\t\telse if (!placed) {\n\t\t\t\tdecls.push([name, text!]);\n\t\t\t\tplaced = true;\n\t\t\t}\n\t\t}\n\t\tif (!placed) decls.push([name, text!]);\n\t\treturn decls.map(([k, v]) => `${k}: ${v}`).join('; ');\n\t}\n\tconst next: Record<string, any> = { ...(style ?? {}) };\n\tif (value === undefined) delete next[prop];\n\telse next[prop] = value;\n\treturn next;\n}\n\nfunction px(value: unknown): number | undefined {\n\tif (typeof value === 'number') return Number.isFinite(value) ? value : undefined;\n\tif (typeof value === 'string') {\n\t\tconst n = parseFloat(value);\n\t\treturn Number.isFinite(n) ? n : undefined;\n\t}\n\treturn undefined;\n}\n\nfunction withStyle(node: CanvasNode, prop: string, value: string | number, unitless = false): CanvasNode {\n\tconst current = readNodeStyleProperty(node.style, prop);\n\tconst written = typeof node.style === 'string' && typeof value === 'number' && !unitless ? `${value}px` : value;\n\tif (typeof node.style === 'string' ? current === String(written) : current === value) return node;\n\treturn { ...node, style: writeNodeStyleProperty(node.style, prop, value, unitless) };\n}\n\nfunction shapeOf(node: CanvasNode): Record<string, any> | null {\n\tconst shape = node.data?.shape;\n\treturn isShapeDescription(shape) ? (shape as Record<string, any>) : null;\n}\n\nfunction withShape(node: CanvasNode, key: string, value: unknown): CanvasNode {\n\tconst shape = shapeOf(node)!;\n\tif (shape[key] === value) return node;\n\treturn { ...node, data: { ...node.data, shape: { ...shape, [key]: value } } };\n}\n\nconst nonNegative = (v: TokenLiteral) => Math.max(0, v as number);\n\nfunction styleOrShape(key: 'fill' | 'stroke' | 'strokeWidth', styleProp: (node: CanvasNode) => string, convert: (v: TokenLiteral) => TokenLiteral = (v) => v) {\n\treturn {\n\t\tread(node: CanvasNode): TokenLiteral | undefined {\n\t\t\tconst shape = shapeOf(node);\n\t\t\tif (shape) return shape[key];\n\t\t\tconst value = readNodeStyleProperty(node.style, styleProp(node));\n\t\t\treturn key === 'strokeWidth' ? px(value) : (value as TokenLiteral | undefined);\n\t\t},\n\t\twrite(node: CanvasNode, value: TokenLiteral): CanvasNode {\n\t\t\tconst v = convert(value);\n\t\t\tif (shapeOf(node)) return withShape(node, key, v);\n\t\t\tlet next = withStyle(node, styleProp(node), v as string | number);\n\t\t\tif (key === 'strokeWidth' && readNodeStyleProperty(next.style, 'borderStyle') === undefined) {\n\t\t\t\tnext = { ...next, style: writeNodeStyleProperty(next.style, 'borderStyle', 'solid') };\n\t\t\t}\n\t\t\treturn next;\n\t\t}\n\t};\n}\n\nfunction backgroundProp(node: CanvasNode): string {\n\treturn readNodeStyleProperty(node.style, 'backgroundColor') === undefined && readNodeStyleProperty(node.style, 'background') !== undefined\n\t\t? 'background'\n\t\t: 'backgroundColor';\n}\n\nfunction layoutProperty(key: string, label: string, read: (l: Record<string, any>) => number | undefined, write: (l: Record<string, any>, v: number) => Record<string, any>): CanvasTokenProperty {\n\treturn {\n\t\tkey,\n\t\tlabel,\n\t\ttype: 'number',\n\t\tscope: 'GAP',\n\t\tappliesTo: (node) => getAutoLayout(node) !== undefined,\n\t\tread: (node) => {\n\t\t\tconst layout = getAutoLayout(node);\n\t\t\treturn layout ? read(layout as Record<string, any>) : undefined;\n\t\t},\n\t\twrite: (node, value) => {\n\t\t\tconst layout = getAutoLayout(node) as Record<string, any> | undefined;\n\t\t\tif (!layout || read(layout) === value) return node;\n\t\t\treturn { ...node, data: { ...node.data, [AUTO_LAYOUT_DATA_KEY]: write(layout, value as number) } };\n\t\t}\n\t};\n}\n\ntype Side = 'top' | 'right' | 'bottom' | 'left';\nconst SIDES: Side[] = ['top', 'right', 'bottom', 'left'];\n\nfunction paddingProperty(key: string, label: string, sides: Side[]): CanvasTokenProperty {\n\treturn layoutProperty(\n\t\tkey,\n\t\tlabel,\n\t\t(layout) => {\n\t\t\tconst p = resolvePadding(layout.padding);\n\t\t\tconst values = sides.map((s) => p[SIDES.indexOf(s)]);\n\t\t\treturn values.every((v) => v === values[0]) ? values[0] : undefined;\n\t\t},\n\t\t(layout, value) => {\n\t\t\tconst p = resolvePadding(layout.padding);\n\t\t\tconst v = Math.max(0, value);\n\t\t\tconst padding = { top: p[0], right: p[1], bottom: p[2], left: p[3] };\n\t\t\tfor (const s of sides) padding[s] = v;\n\t\t\treturn { ...layout, padding };\n\t\t}\n\t);\n}\n\n/** The built-in bindable properties. */\nexport const CANVAS_TOKEN_PROPERTIES: readonly CanvasTokenProperty[] = [\n\t{\n\t\tkey: 'fill',\n\t\tlabel: 'Fill',\n\t\ttype: 'color',\n\t\tscope: (node) => (shapeOf(node) ? 'SHAPE_FILL' : node.type === 'text' ? 'TEXT_FILL' : 'FRAME_FILL'),\n\t\tappliesTo: () => true,\n\t\t...styleOrShape('fill', backgroundProp)\n\t},\n\t{\n\t\tkey: 'stroke',\n\t\tlabel: 'Stroke',\n\t\ttype: 'color',\n\t\tscope: 'STROKE_COLOR',\n\t\tappliesTo: () => true,\n\t\t...styleOrShape('stroke', () => 'borderColor')\n\t},\n\t{\n\t\tkey: 'strokeWidth',\n\t\tlabel: 'Stroke width',\n\t\ttype: 'number',\n\t\tscope: 'STROKE_FLOAT',\n\t\tappliesTo: () => true,\n\t\t...styleOrShape('strokeWidth', () => 'borderWidth', nonNegative)\n\t},\n\t{\n\t\tkey: 'opacity',\n\t\tlabel: 'Opacity',\n\t\ttype: 'number',\n\t\tscope: 'OPACITY',\n\t\tappliesTo: () => true,\n\t\t// Opacity variables are percentages (figcraft / Figma): 40 → 0.4, clamped to 0–100.\n\t\tread: (node) => {\n\t\t\tconst v = px(readNodeStyleProperty(node.style, 'opacity'));\n\t\t\treturn v === undefined ? undefined : Math.round(v * 10000) / 100;\n\t\t},\n\t\twrite: (node, value) => withStyle(node, 'opacity', Math.min(1, Math.max(0, (value as number) / 100)), true)\n\t},\n\t{\n\t\tkey: 'cornerRadius',\n\t\tlabel: 'Corner radius',\n\t\ttype: 'number',\n\t\tscope: 'CORNER_RADIUS',\n\t\tappliesTo: (node) => {\n\t\t\tconst shape = shapeOf(node);\n\t\t\treturn !shape || shape.kind === 'rect';\n\t\t},\n\t\tread: (node) => {\n\t\t\tconst shape = shapeOf(node);\n\t\t\tif (shape) return typeof shape.cornerRadius === 'number' ? shape.cornerRadius : undefined;\n\t\t\treturn px(readNodeStyleProperty(node.style, 'borderRadius'));\n\t\t},\n\t\twrite: (node, value) => {\n\t\t\tconst v = Math.max(0, value as number);\n\t\t\treturn shapeOf(node) ? withShape(node, 'cornerRadius', v) : withStyle(node, 'borderRadius', v);\n\t\t}\n\t},\n\tlayoutProperty('gap', 'Gap', (l) => (typeof l.gap === 'number' ? l.gap : 0), (l, v) => ({ ...l, gap: v })),\n\tlayoutProperty('rowGap', 'Row gap', (l) => (typeof l.rowGap === 'number' ? l.rowGap : undefined), (l, v) => ({ ...l, rowGap: v })),\n\tpaddingProperty('padding', 'Padding', ['top', 'right', 'bottom', 'left']),\n\tpaddingProperty('paddingHorizontal', 'Horizontal padding', ['left', 'right']),\n\tpaddingProperty('paddingVertical', 'Vertical padding', ['top', 'bottom']),\n\tpaddingProperty('paddingTop', 'Top padding', ['top']),\n\tpaddingProperty('paddingRight', 'Right padding', ['right']),\n\tpaddingProperty('paddingBottom', 'Bottom padding', ['bottom']),\n\tpaddingProperty('paddingLeft', 'Left padding', ['left']),\n\t{\n\t\tkey: 'textColor',\n\t\tlabel: 'Text colour',\n\t\ttype: 'color',\n\t\tscope: 'TEXT_FILL',\n\t\tappliesTo: (node) => !shapeOf(node),\n\t\tread: (node) => readNodeStyleProperty(node.style, 'color') as string | undefined,\n\t\twrite: (node, value) => withStyle(node, 'color', value as string)\n\t},\n\t{\n\t\tkey: 'fontSize',\n\t\tlabel: 'Font size',\n\t\ttype: 'number',\n\t\tscope: 'FONT_SIZE',\n\t\tappliesTo: (node) => !shapeOf(node),\n\t\tread: (node) => px(readNodeStyleProperty(node.style, 'fontSize')),\n\t\twrite: (node, value) => withStyle(node, 'fontSize', Math.max(0, value as number))\n\t},\n\t{\n\t\tkey: 'visible',\n\t\tlabel: 'Visible',\n\t\ttype: 'boolean',\n\t\tappliesTo: () => true,\n\t\tread: (node) => !node.hidden,\n\t\twrite: (node, value) => (!node.hidden === value ? node : { ...node, hidden: !value })\n\t}\n];\n\nexport interface CanvasTokenOptions {\n\t/** Modes for nodes without an explicit mode up their tree (e.g. the page's). */\n\tmodes?: TokenModeContext;\n\t/** Bindable properties (default {@link CANVAS_TOKEN_PROPERTIES}); add your own to extend. */\n\tproperties?: readonly CanvasTokenProperty[];\n}\n\nconst EMPTY: Readonly<Record<string, string>> = Object.freeze({});\n\nfunction stringRecord(value: unknown): Readonly<Record<string, string>> {\n\tif (typeof value !== 'object' || value === null || Array.isArray(value)) return EMPTY;\n\tconst out: Record<string, string> = {};\n\tfor (const [k, v] of Object.entries(value)) if (typeof v === 'string') out[k] = v;\n\treturn out;\n}\n\n/** The node's property → variable id bindings. */\nexport function getNodeTokenBindings(node: CanvasNode | undefined | null): Readonly<Record<string, string>> {\n\treturn stringRecord(node?.data?.[TOKEN_BINDINGS_DATA_KEY]);\n}\n\n/** The node's own explicit modes (collection id → mode id). */\nexport function getNodeTokenModes(node: CanvasNode | undefined | null): Readonly<Record<string, string>> {\n\treturn stringRecord(node?.data?.[TOKEN_MODES_DATA_KEY]);\n}\n\nexport function getCanvasTokenProperty(key: string, options: CanvasTokenOptions = {}): CanvasTokenProperty | undefined {\n\treturn (options.properties ?? CANVAS_TOKEN_PROPERTIES).find((p) => p.key === key);\n}\n\n/** Properties a variable can be bound to on this node. */\nexport function getBindableTokenProperties(node: CanvasNode, options: CanvasTokenOptions = {}): CanvasTokenProperty[] {\n\treturn (options.properties ?? CANVAS_TOKEN_PROPERTIES).filter((p) => p.appliesTo(node));\n}\n\n/** The scope of a property on a node. */\nexport function getTokenPropertyScope(property: CanvasTokenProperty, node: CanvasNode): TokenScope | undefined {\n\treturn typeof property.scope === 'function' ? property.scope(node) : property.scope;\n}\n\n/** Variables a picker offers for `property` on `node`: the property's type, in scope. */\nexport function getTokensForProperty(state: TokenState, property: CanvasTokenProperty, node: CanvasNode): TokenVariable[] {\n\tconst scope = getTokenPropertyScope(property, node);\n\treturn state.variables.filter((v) => v.type === property.type && isTokenInScope(v, scope));\n}\n\nfunction lookupOf(nodes: readonly CanvasNode[]): Map<string, CanvasNode> {\n\treturn new Map(nodes.map((n) => [n.id, n]));\n}\n\nfunction validModes(state: TokenState | undefined, modes: Readonly<Record<string, string>>, into: Record<string, string>): void {\n\tfor (const [collectionId, modeId] of Object.entries(modes)) {\n\t\tif (state) {\n\t\t\tconst collection = getTokenCollection(state, collectionId);\n\t\t\t// An unknown collection or mode counts as \"Auto\" (figcraft resets them to Auto).\n\t\t\tif (!collection || !collection.modes.some((m) => m.id === modeId)) continue;\n\t\t}\n\t\tinto[collectionId] = modeId;\n\t}\n}\n\n/**\n * The modes `node` is in: the root `modes`, overridden by each ancestor's explicit modes from\n * the top down, then the node's own. Modes `state` doesn't know are ignored.\n */\nexport function getEffectiveTokenModes(\n\tnode: CanvasNode,\n\tnodes: readonly CanvasNode[] | ReadonlyMap<string, CanvasNode>,\n\tstate?: TokenState,\n\toptions: Pick<CanvasTokenOptions, 'modes'> = {}\n): Record<string, string> {\n\tconst lookup = nodes instanceof Map ? nodes : lookupOf(nodes as readonly CanvasNode[]);\n\tconst chain: CanvasNode[] = [];\n\tconst seen = new Set<string>();\n\tfor (let at: CanvasNode | undefined = node; at && !seen.has(at.id); at = at.parentId ? lookup.get(at.parentId) : undefined) {\n\t\tseen.add(at.id);\n\t\tchain.push(at);\n\t}\n\tconst out: Record<string, string> = {};\n\tif (options.modes) validModes(state, options.modes, out);\n\tfor (let i = chain.length - 1; i >= 0; i--) validModes(state, getNodeTokenModes(chain[i]), out);\n\treturn out;\n}\n\nfunction modesResolver(nodes: readonly CanvasNode[], state: TokenState, options: CanvasTokenOptions) {\n\tconst lookup = lookupOf(nodes);\n\tconst memo = new Map<string, Record<string, string>>();\n\tconst root: Record<string, string> = {};\n\tif (options.modes) validModes(state, options.modes, root);\n\tconst visiting = new Set<string>();\n\tconst modesOf = (node: CanvasNode): Record<string, string> => {\n\t\tconst cached = memo.get(node.id);\n\t\tif (cached) return cached;\n\t\tlet base = root;\n\t\tif (node.parentId && !visiting.has(node.id)) {\n\t\t\tconst parent = lookup.get(node.parentId);\n\t\t\tif (parent) {\n\t\t\t\tvisiting.add(node.id);\n\t\t\t\tbase = modesOf(parent);\n\t\t\t\tvisiting.delete(node.id);\n\t\t\t}\n\t\t}\n\t\tconst own = getNodeTokenModes(node);\n\t\tlet result = base;\n\t\tif (Object.keys(own).length > 0) {\n\t\t\tresult = { ...base };\n\t\t\tvalidModes(state, own, result);\n\t\t}\n\t\tmemo.set(node.id, result);\n\t\treturn result;\n\t};\n\treturn { lookup, modesOf };\n}\n\nfunction applyBindings(node: CanvasNode, state: TokenState, modes: TokenModeContext, options: CanvasTokenOptions, only?: ReadonlySet<string>): CanvasNode {\n\tconst bindings = getNodeTokenBindings(node);\n\tlet next = node;\n\tfor (const [key, variableId] of Object.entries(bindings)) {\n\t\tif (only && !only.has(variableId)) continue;\n\t\tconst property = getCanvasTokenProperty(key, options);\n\t\tif (!property || !property.appliesTo(next)) continue;\n\t\tconst resolved = resolveToken(state, variableId, modes);\n\t\tif (!resolved.ok || resolved.type !== property.type) continue;\n\t\tnext = property.write(next, resolved.value);\n\t}\n\treturn next;\n}\n\n/**\n * Writes every bound property's resolved value into its node. Returns the same array when\n * nothing changed (unchanged nodes keep their identity), so it is safe to run on every change.\n * Bindings that don't resolve (deleted variable, cycle) keep the node's current value.\n */\nexport function resolveCanvasTokens<TNode extends CanvasNode>(nodes: readonly TNode[], state: TokenState, options: CanvasTokenOptions = {}): TNode[] {\n\tconst { modesOf } = modesResolver(nodes, state, options);\n\tlet changed = false;\n\tconst out = nodes.map((node) => {\n\t\tif (Object.keys(getNodeTokenBindings(node)).length === 0) return node;\n\t\tconst next = applyBindings(node, state, modesOf(node), options);\n\t\tif (next !== node) changed = true;\n\t\treturn next as TNode;\n\t});\n\treturn changed ? out : (nodes as TNode[]);\n}\n\nexport interface CanvasTokenBindingInfo {\n\tproperty: CanvasTokenProperty;\n\tvariableId: string;\n\tvariable: TokenVariable | undefined;\n\tresolution: TokenResolution;\n}\n\n/** Each binding of a node with its variable and resolution (for inspectors and diagnostics). */\nexport function inspectNodeTokens(\n\tnode: CanvasNode,\n\tnodes: readonly CanvasNode[],\n\tstate: TokenState,\n\toptions: CanvasTokenOptions = {}\n): CanvasTokenBindingInfo[] {\n\tconst modes = getEffectiveTokenModes(node, nodes, state, options);\n\tconst out: CanvasTokenBindingInfo[] = [];\n\tfor (const [key, variableId] of Object.entries(getNodeTokenBindings(node))) {\n\t\tconst property = getCanvasTokenProperty(key, options);\n\t\tif (!property) continue;\n\t\tout.push({ property, variableId, variable: getTokenVariable(state, variableId), resolution: resolveToken(state, variableId, modes) });\n\t}\n\treturn out;\n}\n\nfunction setDataRecord(node: CanvasNode, key: string, record: Record<string, string>): CanvasNode {\n\tconst data = { ...node.data };\n\tif (Object.keys(record).length === 0) delete data[key];\n\telse data[key] = record;\n\treturn { ...node, data };\n}\n\n/**\n * Binds `property` of each node in `ids` to `variableId` (or unbinds with `null`, keeping the\n * current value) and resolves. Throws `type-mismatch` when the variable's type doesn't fit the\n * property, `not-found` for unknown variables or properties. Nodes the property doesn't apply\n * to are left alone.\n */\nexport function bindCanvasToken<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tids: Iterable<string>,\n\tpropertyKey: string,\n\tvariableId: string | null,\n\tstate: TokenState,\n\toptions: CanvasTokenOptions = {}\n): TNode[] {\n\tconst property = getCanvasTokenProperty(propertyKey, options);\n\tif (!property) throw new TokenError('not-found', `No bindable property \\`${propertyKey}\\``);\n\tif (variableId !== null) {\n\t\tconst variable = getTokenVariable(state, variableId);\n\t\tif (!variable) throw new TokenError('not-found', `No variable \\`${variableId}\\``);\n\t\tif (variable.type !== property.type) {\n\t\t\tthrow new TokenError('type-mismatch', `${property.label} takes a ${property.type} variable; \\`${variable.name}\\` is a ${variable.type}`);\n\t\t}\n\t}\n\tconst targets = new Set(ids);\n\tlet changed = false;\n\tconst bound = nodes.map((node) => {\n\t\tif (!targets.has(node.id) || !property.appliesTo(node)) return node;\n\t\tconst bindings = { ...getNodeTokenBindings(node) };\n\t\tif (variableId === null) {\n\t\t\tif (!(propertyKey in bindings)) return node;\n\t\t\tdelete bindings[propertyKey];\n\t\t} else {\n\t\t\tif (bindings[propertyKey] === variableId) return node;\n\t\t\tbindings[propertyKey] = variableId;\n\t\t}\n\t\tchanged = true;\n\t\treturn setDataRecord(node, TOKEN_BINDINGS_DATA_KEY, bindings) as TNode;\n\t});\n\treturn changed ? resolveCanvasTokens(bound, state, options) : (nodes as TNode[]);\n}\n\n/**\n * Sets (or with `null` clears, back to \"Auto\") the explicit mode of `collectionId` on each node\n * in `ids`, then resolves, so descendants follow.\n */\nexport function setCanvasTokenMode<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tids: Iterable<string>,\n\tcollectionId: string,\n\tmodeId: string | null,\n\tstate: TokenState,\n\toptions: CanvasTokenOptions = {}\n): TNode[] {\n\tif (modeId !== null) {\n\t\tconst collection = getTokenCollection(state, collectionId);\n\t\tif (!collection) throw new TokenError('not-found', `No collection \\`${collectionId}\\``);\n\t\tif (!collection.modes.some((m) => m.id === modeId)) throw new TokenError('not-found', `No mode \\`${modeId}\\` in \\`${collection.name}\\``);\n\t}\n\tconst targets = new Set(ids);\n\tlet changed = false;\n\tconst next = nodes.map((node) => {\n\t\tif (!targets.has(node.id)) return node;\n\t\tconst modes = { ...getNodeTokenModes(node) };\n\t\tif (modeId === null) {\n\t\t\tif (!(collectionId in modes)) return node;\n\t\t\tdelete modes[collectionId];\n\t\t} else {\n\t\t\tif (modes[collectionId] === modeId) return node;\n\t\t\tmodes[collectionId] = modeId;\n\t\t}\n\t\tchanged = true;\n\t\treturn setDataRecord(node, TOKEN_MODES_DATA_KEY, modes) as TNode;\n\t});\n\treturn changed ? resolveCanvasTokens(next, state, options) : (nodes as TNode[]);\n}\n\n/**\n * Detaches bindings to `variableIds` (default: every binding whose variable is missing from\n * `state`): each property keeps its value resolved in `state` and the binding is removed. Pass\n * the state from *before* a delete (`TokenChangeEvent.previous`) so deleted variables still\n * resolve.\n */\nexport function detachTokenBindings<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tstate: TokenState,\n\tvariableIds?: Iterable<string>,\n\toptions: CanvasTokenOptions = {}\n): TNode[] {\n\tconst only = variableIds ? new Set(variableIds) : null;\n\tconst isDetached = (id: string) => (only ? only.has(id) : !getTokenVariable(state, id));\n\tconst { modesOf } = modesResolver(nodes, state, options);\n\tlet changed = false;\n\tconst out = nodes.map((node) => {\n\t\tconst bindings = getNodeTokenBindings(node);\n\t\tconst gone = Object.entries(bindings).filter(([, id]) => isDetached(id));\n\t\tif (gone.length === 0) return node;\n\t\tlet next = applyBindings(node, state, modesOf(node), options, new Set(gone.map(([, id]) => id)));\n\t\tconst kept = { ...bindings };\n\t\tfor (const [key] of gone) delete kept[key];\n\t\tnext = setDataRecord(next, TOKEN_BINDINGS_DATA_KEY, kept);\n\t\tchanged = true;\n\t\treturn next as TNode;\n\t});\n\treturn changed ? out : (nodes as TNode[]);\n}\n\n/**\n * Removes explicit modes `state` no longer knows (deleted collections or modes), like figcraft\n * resetting such layers to \"Auto\". Resolution already ignores them; this tidies the data.\n */\nexport function pruneCanvasTokenModes<TNode extends CanvasNode>(nodes: readonly TNode[], state: TokenState): TNode[] {\n\tlet changed = false;\n\tconst out = nodes.map((node) => {\n\t\tconst modes = getNodeTokenModes(node);\n\t\tconst valid: Record<string, string> = {};\n\t\tvalidModes(state, modes, valid);\n\t\tif (Object.keys(valid).length === Object.keys(modes).length) return node;\n\t\tchanged = true;\n\t\treturn setDataRecord(node, TOKEN_MODES_DATA_KEY, valid) as TNode;\n\t});\n\treturn changed ? out : (nodes as TNode[]);\n}\n","// Portions ported from filmcraft-main/crates/project/src/keyframe.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport { getNodeSize, readStyleSize, withStyleSize } from '../arrange.js';\nimport { bezierPathLength, bezierPathPointAtLength } from '../pathops/bezier.js';\nimport { getCanvasTokenProperty, readNodeStyleProperty, writeNodeStyleProperty } from '../tokenBindings.js';\nimport { withStyleOpacity } from '../trajectory.js';\nimport type { CanvasNode } from '../types.js';\nimport { ANIMATION_TIME_EPSILON, isAnimationKeyframe, normalizeKeyframes, type AnimationKeyframe, type AnimationTrack, type AnimationValue, type AnimationValueType } from '../../animation/keyframes.js';\n\nexport { ANIMATION_TIME_EPSILON, isAnimationKeyframe, normalizeKeyframes, type AnimationKeyframe, type AnimationTrack, type AnimationValue, type AnimationValueType };\n\n/**\n * Keyframe animation for canvas nodes.\n *\n * A node's animation lives in `node.data.animation`, so it is copied, pasted, duplicated and\n * serialised with the node (component instances copy it too). Each animatable property has a\n * track of keyframes `{ time (ms), value, easing }`; the easing governs the segment *leaving*\n * the keyframe (filmcraft / After Effects convention).\n */\n\n/** Data key holding a node's animation. */\nexport const ANIMATION_DATA_KEY = 'animation';\n\n/** A path a node's centre follows, driven by the `motionPath` track (progress 0–1). */\nexport interface AnimationMotionPath {\n\t/** SVG path data in flow coordinates (the parent's when the node has a `parentId`). */\n\td: string;\n\t/** Rotate the node along the path's tangent (default `false`). */\n\tautoRotate?: boolean;\n}\n\nexport interface NodeAnimation {\n\t/** Property key → track. */\n\ttracks: Record<string, AnimationTrack>;\n\tmotionPath?: AnimationMotionPath;\n}\n\n/** A node property keyframes can drive. */\nexport interface AnimatableProperty {\n\tkey: string;\n\tlabel: string;\n\ttype: AnimationValueType;\n\t/** Values are clamped into `[min, max]` when written. */\n\tmin?: number;\n\tmax?: number;\n\t/** Unit for display (`px`, `°`, …). */\n\tunit?: string;\n\t/** The node's current (static) value, or `undefined` when it has none. */\n\tread(node: CanvasNode): AnimationValue | undefined;\n\t/** Returns the node with `value` written (a new object; the input is never mutated). */\n\twrite(node: CanvasNode, value: AnimationValue): CanvasNode;\n}\n\nconst num = (value: unknown): number | undefined => {\n\tif (typeof value === 'number') return Number.isFinite(value) ? value : undefined;\n\tif (typeof value === 'string') {\n\t\tconst n = parseFloat(value);\n\t\treturn Number.isFinite(n) ? n : undefined;\n\t}\n\treturn undefined;\n};\n\nconst tidy = (v: number) => Math.round(v * 10000) / 10000;\n\nfunction tokenBacked(key: string, label: string, type: AnimationValueType, extra: Partial<AnimatableProperty> = {}): AnimatableProperty {\n\treturn {\n\t\tkey,\n\t\tlabel,\n\t\ttype,\n\t\t...extra,\n\t\tread: (node) => {\n\t\t\tconst p = getCanvasTokenProperty(key);\n\t\t\tconst v = p?.appliesTo(node) ? p.read(node) : undefined;\n\t\t\treturn typeof v === 'number' || typeof v === 'string' ? v : undefined;\n\t\t},\n\t\twrite: (node, value) => {\n\t\t\tconst p = getCanvasTokenProperty(key);\n\t\t\treturn p?.appliesTo(node) ? p.write(node, value) : node;\n\t\t}\n\t};\n}\n\nfunction sizeProperty(key: 'width' | 'height', label: string): AnimatableProperty {\n\treturn {\n\t\tkey,\n\t\tlabel,\n\t\ttype: 'number',\n\t\tmin: 0,\n\t\tunit: 'px',\n\t\tread: (node) => num(node[key]) ?? readStyleSize(node.style)[key] ?? num(node.measured?.[key]),\n\t\twrite: (node, value) => {\n\t\t\tconst v = tidy(Math.max(0, value as number));\n\t\t\treturn { ...node, [key]: v, style: withStyleSize(node.style, { [key]: v }) };\n\t\t}\n\t};\n}\n\nfunction motionPathPoint(node: CanvasNode, progress: number) {\n\tconst path = getNodeAnimation(node)?.motionPath;\n\tif (!path || typeof path.d !== 'string') return null;\n\ttry {\n\t\tconst length = bezierPathLength(path.d);\n\t\treturn bezierPathPointAtLength(path.d, Math.min(1, Math.max(0, progress)) * length);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * The built-in animatable properties, in the order they are applied (`motionPath` comes after\n * `x` / `y` / `rotation` so it wins over them).\n *\n * Rotation and scale use the CSS individual transform properties (`style.rotate`,\n * `style.scale`), which compose with the `transform` xyflow positions nodes with.\n */\nexport const ANIMATABLE_PROPERTIES: readonly AnimatableProperty[] = [\n\t{\n\t\tkey: 'x',\n\t\tlabel: 'X',\n\t\ttype: 'number',\n\t\tunit: 'px',\n\t\tread: (node) => num(node.position?.x),\n\t\twrite: (node, value) => ({ ...node, position: { ...node.position, x: tidy(value as number) } })\n\t},\n\t{\n\t\tkey: 'y',\n\t\tlabel: 'Y',\n\t\ttype: 'number',\n\t\tunit: 'px',\n\t\tread: (node) => num(node.position?.y),\n\t\twrite: (node, value) => ({ ...node, position: { ...node.position, y: tidy(value as number) } })\n\t},\n\tsizeProperty('width', 'Width'),\n\tsizeProperty('height', 'Height'),\n\t{\n\t\tkey: 'rotation',\n\t\tlabel: 'Rotation',\n\t\ttype: 'number',\n\t\tunit: '°',\n\t\tread: (node) => num(readNodeStyleProperty(node.style, 'rotate')) ?? 0,\n\t\twrite: (node, value) => ({\n\t\t\t...node,\n\t\t\tstyle: writeNodeStyleProperty(node.style, 'rotate', `${tidy(value as number)}deg`)\n\t\t})\n\t},\n\t{\n\t\tkey: 'scale',\n\t\tlabel: 'Scale',\n\t\ttype: 'number',\n\t\tmin: 0,\n\t\tread: (node) => num(readNodeStyleProperty(node.style, 'scale')) ?? 1,\n\t\twrite: (node, value) => ({\n\t\t\t...node,\n\t\t\tstyle: writeNodeStyleProperty(node.style ?? {}, 'scale', tidy(Math.max(0, value as number)), true)\n\t\t})\n\t},\n\t{\n\t\tkey: 'opacity',\n\t\tlabel: 'Opacity',\n\t\ttype: 'number',\n\t\tmin: 0,\n\t\tmax: 1,\n\t\tread: (node) => num(readNodeStyleProperty(node.style, 'opacity')) ?? 1,\n\t\twrite: (node, value) => ({\n\t\t\t...node,\n\t\t\tstyle: withStyleOpacity(node.style, tidy(Math.min(1, Math.max(0, value as number))), typeof node.style === 'string' ? 'string' : 'object')\n\t\t})\n\t},\n\ttokenBacked('fill', 'Fill', 'color'),\n\ttokenBacked('stroke', 'Stroke', 'color'),\n\ttokenBacked('strokeWidth', 'Stroke width', 'number', { min: 0, unit: 'px' }),\n\ttokenBacked('cornerRadius', 'Corner radius', 'number', { min: 0, unit: 'px' }),\n\t{\n\t\tkey: 'motionPath',\n\t\tlabel: 'Motion path',\n\t\ttype: 'number',\n\t\tmin: 0,\n\t\tmax: 1,\n\t\tread: () => undefined,\n\t\twrite: (node, value) => {\n\t\t\tconst at = motionPathPoint(node, value as number);\n\t\t\tif (!at) return node;\n\t\t\tconst { width, height } = getNodeSize(node);\n\t\t\tlet next: CanvasNode = {\n\t\t\t\t...node,\n\t\t\t\tposition: { x: tidy(at.point[0] - width / 2), y: tidy(at.point[1] - height / 2) }\n\t\t\t};\n\t\t\tif (getNodeAnimation(node)?.motionPath?.autoRotate) {\n\t\t\t\tconst base = num(readNodeStyleProperty(node.style, 'rotate')) ?? 0;\n\t\t\t\tnext = { ...next, style: writeNodeStyleProperty(next.style, 'rotate', `${tidy(base + (at.angle * 180) / Math.PI)}deg`) };\n\t\t\t}\n\t\t\treturn next;\n\t\t}\n\t}\n];\n\nexport interface AnimationPropertyOptions {\n\t/** Animatable properties (default {@link ANIMATABLE_PROPERTIES}); add your own to extend. */\n\tproperties?: readonly AnimatableProperty[];\n}\n\nexport function getAnimatableProperty(key: string, options: AnimationPropertyOptions = {}): AnimatableProperty | undefined {\n\treturn (options.properties ?? ANIMATABLE_PROPERTIES).find((p) => p.key === key);\n}\n\n/** Clamps a value into a property's range (numbers) or returns it unchanged. */\nexport function clampAnimationValue(property: AnimatableProperty | undefined, value: AnimationValue): AnimationValue {\n\tif (!property || typeof value !== 'number') return value;\n\tlet v = value;\n\tif (property.min !== undefined) v = Math.max(property.min, v);\n\tif (property.max !== undefined) v = Math.min(property.max, v);\n\treturn v;\n}\n\n/** The node's animation (tracks with at least one valid keyframe), or `null`. */\nexport function getNodeAnimation(node: CanvasNode | undefined | null): NodeAnimation | null {\n\tconst raw = node?.data?.[ANIMATION_DATA_KEY];\n\tif (typeof raw !== 'object' || raw === null || typeof raw.tracks !== 'object' || raw.tracks === null) return null;\n\treturn raw as NodeAnimation;\n}\n\n/** The track for `property` on a node, if it has keyframes. */\nexport function getAnimationTrack(node: CanvasNode | undefined | null, property: string): AnimationTrack | undefined {\n\tconst track = getNodeAnimation(node)?.tracks[property];\n\treturn track && Array.isArray(track.keyframes) && normalizeKeyframes(track.keyframes).length > 0 ? track : undefined;\n}\n\n/** Whether the node has any (enabled or disabled) keyframes. */\nexport function isNodeAnimated(node: CanvasNode | undefined | null): boolean {\n\tconst animation = getNodeAnimation(node);\n\tif (!animation) return false;\n\treturn Object.keys(animation.tracks).some((key) => getAnimationTrack(node, key) !== undefined);\n}\n\n/** Property keys of the node's tracks (in registry order, unknown keys last). */\nexport function getAnimatedProperties(node: CanvasNode | undefined | null, options: AnimationPropertyOptions = {}): string[] {\n\tconst animation = getNodeAnimation(node);\n\tif (!animation) return [];\n\tconst keys = Object.keys(animation.tracks).filter((k) => getAnimationTrack(node, k));\n\tconst order = (options.properties ?? ANIMATABLE_PROPERTIES).map((p) => p.key);\n\tconst rank = (k: string) => (order.includes(k) ? order.indexOf(k) : order.length);\n\treturn keys.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));\n}\n\n/**\n * Returns the node with its animation replaced (`null` or no tracks removes the key). Empty\n * tracks are dropped.\n */\nexport function withNodeAnimation<TNode extends CanvasNode>(node: TNode, animation: NodeAnimation | null): TNode {\n\tconst tracks: Record<string, AnimationTrack> = {};\n\tif (animation) {\n\t\tfor (const [key, track] of Object.entries(animation.tracks)) {\n\t\t\tif (track && Array.isArray(track.keyframes) && track.keyframes.length > 0) tracks[key] = track;\n\t\t}\n\t}\n\tconst data: Record<string, any> = { ...(node.data ?? {}) };\n\tif (Object.keys(tracks).length === 0 && !animation?.motionPath) delete data[ANIMATION_DATA_KEY];\n\telse data[ANIMATION_DATA_KEY] = { ...animation, tracks };\n\treturn { ...node, data };\n}\n","// Portions ported from filmcraft-main/crates/project/src/keyframe.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport type { CanvasNode } from '../types.js';\nimport { evaluateTrack } from '../../animation/evaluate.js';\nimport {\n\tANIMATABLE_PROPERTIES,\n\tclampAnimationValue,\n\tgetNodeAnimation,\n\tnormalizeKeyframes,\n\ttype AnimatableProperty,\n\ttype AnimationPropertyOptions,\n\ttype AnimationValue\n} from './model.js';\n\nexport { evaluateTrack, interpolateAnimationValue, wrapAnimationTime, type AnimationLoopMode } from '../../animation/evaluate.js';\n\n/** Nodes, or a document holding them. */\nexport type AnimationDocument = readonly CanvasNode[] | { readonly nodes: readonly CanvasNode[] };\n\n/** Evaluated values: node id → property key → value. */\nexport type AnimationOverrides = Record<string, Record<string, AnimationValue>>;\n\nconst nodesOf = (doc: AnimationDocument): readonly CanvasNode[] => (Array.isArray(doc) ? doc : (doc as { nodes: readonly CanvasNode[] }).nodes);\n\n/** The last keyframe time over the enabled tracks of the nodes (0 when nothing is animated). */\nexport function getAnimationDuration(doc: AnimationDocument): number {\n\tlet end = 0;\n\tfor (const node of nodesOf(doc)) {\n\t\tconst animation = getNodeAnimation(node);\n\t\tif (!animation) continue;\n\t\tfor (const track of Object.values(animation.tracks)) {\n\t\t\tif (!track || track.enabled === false) continue;\n\t\t\tconst keys = normalizeKeyframes(track.keyframes);\n\t\t\tif (keys.length > 0) end = Math.max(end, keys[keys.length - 1].time);\n\t\t}\n\t}\n\treturn end;\n}\n\nexport interface EvaluateAnimationOptions extends AnimationPropertyOptions {\n\t/** Only evaluate these nodes. */\n\tids?: Iterable<string>;\n}\n\n/**\n * Evaluates every enabled track at `time`: node id → property → value. The nodes are not\n * touched; apply the result with `applyAnimationOverrides` (or use `applyAnimation`).\n */\nexport function evaluateAnimation(doc: AnimationDocument, time: number, options: EvaluateAnimationOptions = {}): AnimationOverrides {\n\tconst only = options.ids ? new Set(options.ids) : null;\n\tconst out: AnimationOverrides = {};\n\tfor (const node of nodesOf(doc)) {\n\t\tif (only && !only.has(node.id)) continue;\n\t\tconst animation = getNodeAnimation(node);\n\t\tif (!animation) continue;\n\t\tlet values: Record<string, AnimationValue> | undefined;\n\t\tfor (const [key, track] of Object.entries(animation.tracks)) {\n\t\t\tif (!track || track.enabled === false) continue;\n\t\t\tconst property = (options.properties ?? ANIMATABLE_PROPERTIES).find((p) => p.key === key);\n\t\t\tconst value = evaluateTrack(track, time, property?.type ?? 'number');\n\t\t\tif (value === undefined) continue;\n\t\t\t(values ??= {})[key] = clampAnimationValue(property, value);\n\t\t}\n\t\tif (values) out[node.id] = values;\n\t}\n\treturn out;\n}\n\n/**\n * Writes evaluated values into copies of the nodes (in the registry's order). Nodes without\n * overrides keep their identity, and the input array is returned when nothing changes.\n */\nexport function applyAnimationOverrides<TNode extends CanvasNode>(nodes: readonly TNode[], overrides: AnimationOverrides, options: AnimationPropertyOptions = {}): TNode[] {\n\tconst properties: readonly AnimatableProperty[] = options.properties ?? ANIMATABLE_PROPERTIES;\n\tlet changed = false;\n\tconst out = nodes.map((node) => {\n\t\tconst values = overrides[node.id];\n\t\tif (!values) return node;\n\t\tlet next: CanvasNode = node;\n\t\tfor (const property of properties) {\n\t\t\tif (!(property.key in values)) continue;\n\t\t\tnext = property.write(next, values[property.key]);\n\t\t}\n\t\tif (next !== node) changed = true;\n\t\treturn next as TNode;\n\t});\n\treturn changed ? out : (nodes as TNode[]);\n}\n\n/**\n * The nodes as they look at `time`: `applyAnimationOverrides(nodes, evaluateAnimation(nodes, time))`.\n * Run it after `resolveCanvasTokens` so animated values win over token values while playing.\n */\nexport function applyAnimation<TNode extends CanvasNode>(nodes: readonly TNode[], time: number, options: EvaluateAnimationOptions = {}): TNode[] {\n\treturn applyAnimationOverrides(nodes, evaluateAnimation(nodes, time, options), options);\n}\n","// Portions ported from filmcraft-main/crates/engine/src/keyframe_move.rs and crates/project/src/keyframe.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport { getNodeSize } from '../arrange.js';\nimport type { CanvasNode } from '../types.js';\nimport { normalizeTokenColor } from '../../tokens/color.js';\nimport { isAnimationEasing, parseCssEasing, type AnimationEasing } from './easing.js';\nimport { evaluateTrack } from './evaluate.js';\nimport {\n\tANIMATABLE_PROPERTIES,\n\tANIMATION_TIME_EPSILON,\n\tclampAnimationValue,\n\tgetAnimatableProperty,\n\tgetAnimationTrack,\n\tgetNodeAnimation,\n\tnormalizeKeyframes,\n\twithNodeAnimation,\n\ttype AnimatableProperty,\n\ttype AnimationKeyframe,\n\ttype AnimationPropertyOptions,\n\ttype AnimationTrack,\n\ttype AnimationValue,\n\ttype NodeAnimation\n} from './model.js';\n\n/** Identifies one keyframe: node, property and time. */\nexport interface KeyframeRef {\n\tnodeId: string;\n\tproperty: string;\n\ttime: number;\n}\n\nexport type AnimationEditErrorCode =\n\t| 'unknown_node'\n\t| 'unknown_property'\n\t| 'unknown_keyframe'\n\t| 'duplicate_keyframe'\n\t| 'invalid_time'\n\t| 'invalid_value'\n\t| 'invalid_easing'\n\t| 'invalid_factor'\n\t| 'too_many_keyframes'\n\t| 'no_movement_range';\n\n/** Thrown by the editing functions for invalid requests; nothing is changed when it is thrown. */\nexport class AnimationEditError extends Error {\n\treadonly code: AnimationEditErrorCode;\n\tconstructor(code: AnimationEditErrorCode, message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'AnimationEditError';\n\t\tthis.code = code;\n\t}\n}\n\n/** Most keyframes one move / scale / delete may name (as filmcraft's `effects.moveKeyframes`). */\nexport const MAX_KEYFRAME_REFS = 4096;\n\nconst sameTime = (a: number, b: number) => Math.abs(a - b) <= ANIMATION_TIME_EPSILON;\nconst refKey = (r: { nodeId: string; property: string }) => `${r.nodeId}\\u0000${r.property}`;\n\n/** Snaps a time to the nearest frame at `fps`. */\nexport function snapTimeToFrame(time: number, fps: number): number {\n\tif (!(fps > 0)) return time;\n\tconst frame = 1000 / fps;\n\t// Rounded to a microsecond so frame times compare equal (500, not 500.00000000000006).\n\treturn Math.round(Math.round(time / frame) * frame * 1000) / 1000;\n}\n\nfunction coerceValue(property: AnimatableProperty | undefined, value: unknown): AnimationValue {\n\tconst type = property?.type ?? (typeof value === 'string' && normalizeTokenColor(value) !== null ? 'color' : 'number');\n\tif (type === 'color') {\n\t\tconst color = normalizeTokenColor(value);\n\t\tif (color === null) throw new AnimationEditError('invalid_value', `\"${String(value)}\" is not a colour.`);\n\t\treturn color;\n\t}\n\tconst n = typeof value === 'string' && value.trim() !== '' ? Number(value) : value;\n\tif (typeof n !== 'number' || !Number.isFinite(n)) throw new AnimationEditError('invalid_value', `\"${String(value)}\" is not a number.`);\n\treturn clampAnimationValue(property, n);\n}\n\nfunction coerceEasing(easing: unknown): AnimationEasing {\n\tif (typeof easing === 'string') {\n\t\tconst parsed = parseCssEasing(easing);\n\t\tif (parsed) return parsed;\n\t} else if (isAnimationEasing(easing)) return easing;\n\tthrow new AnimationEditError('invalid_easing', `Unknown easing ${JSON.stringify(easing)}.`);\n}\n\nfunction coerceTime(time: unknown): number {\n\tif (typeof time !== 'number' || !Number.isFinite(time)) throw new AnimationEditError('invalid_time', `Invalid time ${String(time)}.`);\n\treturn Math.max(0, time);\n}\n\n/**\n * Copy-on-write editing session over a node array: tracks are normalised (sorted, duplicates\n * merged) when first touched and written back by `commit`.\n */\nclass Draft<TNode extends CanvasNode> {\n\tprivate readonly byId: Map<string, TNode>;\n\tprivate readonly animations = new Map<string, NodeAnimation>();\n\tprivate readonly nodeWrites = new Map<string, TNode>();\n\n\tconstructor(private readonly nodes: readonly TNode[], readonly options: AnimationPropertyOptions = {}) {\n\t\tthis.byId = new Map(nodes.map((n) => [n.id, n]));\n\t}\n\n\tnode(id: string): TNode {\n\t\tconst node = this.nodeWrites.get(id) ?? this.byId.get(id);\n\t\tif (!node) throw new AnimationEditError('unknown_node', `Node \"${id}\" does not exist.`);\n\t\treturn node;\n\t}\n\n\thas(id: string): boolean {\n\t\treturn this.byId.has(id);\n\t}\n\n\tanimation(id: string): NodeAnimation {\n\t\tlet animation = this.animations.get(id);\n\t\tif (!animation) {\n\t\t\tconst current = getNodeAnimation(this.node(id));\n\t\t\tconst tracks: Record<string, AnimationTrack> = {};\n\t\t\tfor (const [key, track] of Object.entries(current?.tracks ?? {})) {\n\t\t\t\tif (track) tracks[key] = { ...track, keyframes: normalizeKeyframes(track.keyframes) };\n\t\t\t}\n\t\t\tanimation = { ...(current ?? {}), tracks };\n\t\t\tthis.animations.set(id, animation);\n\t\t}\n\t\treturn animation;\n\t}\n\n\tkeys(id: string, property: string): AnimationKeyframe[] {\n\t\treturn this.animation(id).tracks[property]?.keyframes ?? [];\n\t}\n\n\ttrack(id: string, property: string): AnimationTrack | undefined {\n\t\treturn this.animation(id).tracks[property];\n\t}\n\n\tsetKeys(id: string, property: string, keyframes: AnimationKeyframe[]) {\n\t\tconst animation = this.animation(id);\n\t\tconst sorted = [...keyframes].sort((a, b) => a.time - b.time);\n\t\tif (sorted.length === 0) delete animation.tracks[property];\n\t\telse animation.tracks[property] = { ...(animation.tracks[property] ?? {}), keyframes: sorted };\n\t}\n\n\tsetMotionPath(id: string, motionPath: NodeAnimation['motionPath'] | undefined) {\n\t\tconst animation = this.animation(id);\n\t\tif (motionPath) animation.motionPath = { ...motionPath };\n\t\telse delete animation.motionPath;\n\t}\n\n\twriteNode(id: string, fn: (node: TNode) => TNode) {\n\t\tthis.nodeWrites.set(id, fn(this.node(id)));\n\t}\n\n\tfind(ref: KeyframeRef): number {\n\t\tconst keys = this.keys(ref.nodeId, ref.property);\n\t\treturn keys.findIndex((k) => sameTime(k.time, ref.time));\n\t}\n\n\tproperty(key: string): AnimatableProperty | undefined {\n\t\treturn getAnimatableProperty(key, this.options);\n\t}\n\n\tcommit(): TNode[] {\n\t\tif (this.animations.size === 0 && this.nodeWrites.size === 0) return this.nodes as TNode[];\n\t\treturn this.nodes.map((node) => {\n\t\t\tconst written = this.nodeWrites.get(node.id);\n\t\t\tconst animation = this.animations.get(node.id);\n\t\t\tif (!written && !animation) return node;\n\t\t\tconst base = written ?? node;\n\t\t\treturn animation ? withNodeAnimation(base, animation) : base;\n\t\t});\n\t}\n}\n\nfunction validateRefs(draft: Draft<CanvasNode>, refs: readonly KeyframeRef[], allowEmpty = false) {\n\tif (!Array.isArray(refs) || (!allowEmpty && refs.length === 0)) throw new AnimationEditError('unknown_keyframe', 'Name at least one keyframe.');\n\tif (refs.length > MAX_KEYFRAME_REFS) throw new AnimationEditError('too_many_keyframes', `At most ${MAX_KEYFRAME_REFS} keyframes per edit.`);\n\tconst seen = new Map<string, number[]>();\n\tfor (const ref of refs) {\n\t\tif (!ref || typeof ref.nodeId !== 'string' || typeof ref.property !== 'string' || typeof ref.time !== 'number' || !Number.isFinite(ref.time)) {\n\t\t\tthrow new AnimationEditError('unknown_keyframe', 'Invalid keyframe reference.');\n\t\t}\n\t\tdraft.node(ref.nodeId);\n\t\tif (draft.find(ref) === -1) throw new AnimationEditError('unknown_keyframe', `No ${ref.property} keyframe at ${ref.time}ms on \"${ref.nodeId}\".`);\n\t\tconst times = seen.get(refKey(ref)) ?? [];\n\t\tif (times.some((t) => sameTime(t, ref.time))) throw new AnimationEditError('duplicate_keyframe', 'A keyframe is named twice.');\n\t\ttimes.push(ref.time);\n\t\tseen.set(refKey(ref), times);\n\t}\n}\n\n/** Static value of a property, for keying (sizes fall back to the measured size). */\nexport function readAnimatableValue(node: CanvasNode, property: AnimatableProperty): AnimationValue | undefined {\n\tconst value = property.read(node);\n\tif (value !== undefined) return value;\n\tif (property.key === 'width' || property.key === 'height') return getNodeSize(node)[property.key];\n\treturn undefined;\n}\n\n/** The value a property shows at `time`: its track's value when animated, else its static value. */\nexport function getAnimatedValue(node: CanvasNode, propertyKey: string, time: number, options: AnimationPropertyOptions = {}): AnimationValue | undefined {\n\tconst property = getAnimatableProperty(propertyKey, options);\n\tconst track = getAnimationTrack(node, propertyKey);\n\tif (track && track.enabled !== false) return evaluateTrack(track, time, property?.type ?? 'number');\n\treturn property ? readAnimatableValue(node, property) : undefined;\n}\n\nexport interface SetKeyframeOptions extends AnimationPropertyOptions {\n\t/** Easing of the segment leaving the keyframe (default: kept, or the previous keyframe's). */\n\teasing?: AnimationEasing | string;\n}\n\nfunction putKey(draft: Draft<any>, nodeId: string, property: string, time: number, value: AnimationValue, easing?: AnimationEasing, inherit = true) {\n\tconst keys = [...draft.keys(nodeId, property)];\n\tconst at = keys.findIndex((k) => sameTime(k.time, time));\n\tif (at >= 0) {\n\t\tkeys[at] = { ...keys[at], value, ...(easing !== undefined ? { easing } : {}) };\n\t} else {\n\t\t// New keys take the easing of the keyframe before them (filmcraft `Param::set_at`).\n\t\tconst before = [...keys].reverse().find((k) => k.time < time);\n\t\tconst inherited = easing ?? (inherit ? before?.easing : undefined);\n\t\tkeys.push({ time, value, ...(inherited !== undefined ? { easing: inherited } : {}) });\n\t}\n\tdraft.setKeys(nodeId, property, keys);\n}\n\n/** Adds a keyframe, or replaces the value (and easing, when given) of the one at `time`. */\nexport function setKeyframe<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tnodeId: string,\n\tproperty: string,\n\ttime: number,\n\tvalue: AnimationValue,\n\toptions: SetKeyframeOptions = {}\n): TNode[] {\n\tconst draft = new Draft(nodes, options);\n\tdraft.node(nodeId);\n\tconst p = draft.property(property);\n\tif (!p) throw new AnimationEditError('unknown_property', `\"${property}\" cannot be animated.`);\n\tputKey(draft, nodeId, property, coerceTime(time), coerceValue(p, value), options.easing === undefined ? undefined : coerceEasing(options.easing));\n\treturn draft.commit();\n}\n\n/**\n * Keys the given properties of the nodes at `time` with the values they show there (the\n * \"add keyframe at playhead\" action). Without `properties`, keys every animated property, or\n * `x` and `y` for nodes that have none. Returns the new nodes and the keyed refs.\n */\nexport function addKeyframesAtTime<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tids: Iterable<string>,\n\ttime: number,\n\toptions: AnimationPropertyOptions & { properties?: readonly AnimatableProperty[]; keys?: readonly string[] } = {}\n): { nodes: TNode[]; refs: KeyframeRef[] } {\n\tconst draft = new Draft(nodes, options);\n\tconst t = coerceTime(time);\n\tconst refs: KeyframeRef[] = [];\n\tfor (const id of ids) {\n\t\tconst node = draft.node(id);\n\t\tconst animated = Object.keys(draft.animation(id).tracks);\n\t\tconst keys = options.keys ?? (animated.length > 0 ? animated : ['x', 'y']);\n\t\tfor (const key of keys) {\n\t\t\tconst p = draft.property(key);\n\t\t\tif (!p) throw new AnimationEditError('unknown_property', `\"${key}\" cannot be animated.`);\n\t\t\tconst value = getAnimatedValue(node, key, t, options);\n\t\t\tif (value === undefined) continue;\n\t\t\tputKey(draft, id, key, t, value);\n\t\t\trefs.push({ nodeId: id, property: key, time: t });\n\t\t}\n\t}\n\treturn { nodes: draft.commit(), refs };\n}\n\nexport interface KeyframePatch {\n\ttime?: number;\n\tvalue?: AnimationValue;\n\teasing?: AnimationEasing | string | null;\n}\n\n/** Changes one keyframe's time, value or easing (`easing: null` resets it to linear). */\nexport function updateKeyframe<TNode extends CanvasNode>(nodes: readonly TNode[], ref: KeyframeRef, patch: KeyframePatch, options: AnimationPropertyOptions = {}): TNode[] {\n\tconst draft = new Draft(nodes, options);\n\tvalidateRefs(draft, [ref]);\n\tconst keys = [...draft.keys(ref.nodeId, ref.property)];\n\tconst i = keys.findIndex((k) => sameTime(k.time, ref.time));\n\tconst next: AnimationKeyframe = { ...keys[i] };\n\tif (patch.value !== undefined) next.value = coerceValue(draft.property(ref.property), patch.value);\n\tif (patch.easing === null) delete next.easing;\n\telse if (patch.easing !== undefined) next.easing = coerceEasing(patch.easing);\n\tif (patch.time !== undefined) next.time = coerceTime(patch.time);\n\tkeys.splice(i, 1);\n\t// Moving onto another keyframe replaces it.\n\tconst rest = keys.filter((k) => !sameTime(k.time, next.time));\n\tdraft.setKeys(ref.nodeId, ref.property, [...rest, next]);\n\treturn draft.commit();\n}\n\n/** Sets the easing of the segments leaving the keyframes. */\nexport function setKeyframeEasing<TNode extends CanvasNode>(nodes: readonly TNode[], refs: readonly KeyframeRef[], easing: AnimationEasing | string | null, options: AnimationPropertyOptions = {}): TNode[] {\n\tconst draft = new Draft(nodes, options);\n\tvalidateRefs(draft, refs);\n\tconst value = easing === null ? null : coerceEasing(easing);\n\tfor (const ref of refs) {\n\t\tconst keys = draft.keys(ref.nodeId, ref.property).map((k) => {\n\t\t\tif (!sameTime(k.time, ref.time)) return k;\n\t\t\tconst { easing: _old, ...rest } = k;\n\t\t\treturn value === null ? rest : { ...rest, easing: value };\n\t\t});\n\t\tdraft.setKeys(ref.nodeId, ref.property, keys);\n\t}\n\treturn draft.commit();\n}\n\n/**\n * Deletes keyframes. When a track loses its last keyframe the node keeps that keyframe's\n * value as its static value, so it does not jump (filmcraft `remove_keyframe_at`).\n */\nexport function deleteKeyframes<TNode extends CanvasNode>(nodes: readonly TNode[], refs: readonly KeyframeRef[], options: AnimationPropertyOptions = {}): TNode[] {\n\tconst draft = new Draft(nodes, options);\n\tvalidateRefs(draft, refs);\n\tconst lanes = new Map<string, KeyframeRef[]>();\n\tfor (const ref of refs) lanes.set(refKey(ref), [...(lanes.get(refKey(ref)) ?? []), ref]);\n\tfor (const lane of lanes.values()) {\n\t\tconst { nodeId, property } = lane[0];\n\t\tconst keys = draft.keys(nodeId, property);\n\t\tconst left = keys.filter((k) => !lane.some((r) => sameTime(r.time, k.time)));\n\t\tif (left.length === 0) {\n\t\t\tconst p = draft.property(property);\n\t\t\tconst last = keys.find((k) => sameTime(k.time, lane[lane.length - 1].time)) ?? keys[keys.length - 1];\n\t\t\tif (p && property !== 'motionPath') draft.writeNode(nodeId, (n) => p.write(n, last.value) as typeof n);\n\t\t}\n\t\tdraft.setKeys(nodeId, property, left);\n\t}\n\treturn draft.commit();\n}\n\n/** Turns a track on or off (disabled tracks keep their keyframes but do not animate). */\nexport function setTrackEnabled<TNode extends CanvasNode>(nodes: readonly TNode[], nodeId: string, property: string, enabled: boolean): TNode[] {\n\tconst draft = new Draft(nodes);\n\tif (!draft.track(nodeId, property)) throw new AnimationEditError('unknown_property', `\"${nodeId}\" has no ${property} track.`);\n\tconst { enabled: _was, ...track } = draft.track(nodeId, property)!;\n\tdraft.animation(nodeId).tracks[property] = enabled ? track : { ...track, enabled: false };\n\treturn draft.commit();\n}\n\n/**\n * Removes a track. With `keepValueAt`, the node keeps the value the track had at that time\n * (filmcraft's stopwatch toggle).\n */\nexport function removeTrack<TNode extends CanvasNode>(nodes: readonly TNode[], nodeId: string, property: string, options: AnimationPropertyOptions & { keepValueAt?: number } = {}): TNode[] {\n\tconst draft = new Draft(nodes, options);\n\tconst track = draft.track(nodeId, property);\n\tif (!track) return nodes as TNode[];\n\tconst p = draft.property(property);\n\tif (options.keepValueAt !== undefined && p && property !== 'motionPath') {\n\t\tconst value = evaluateTrack(track, options.keepValueAt, p.type);\n\t\tif (value !== undefined) draft.writeNode(nodeId, (n) => p.write(n, value) as typeof n);\n\t}\n\tdraft.setKeys(nodeId, property, []);\n\tif (property === 'motionPath') draft.setMotionPath(nodeId, undefined);\n\treturn draft.commit();\n}\n\n/** Sets (or with `null` removes) the path the `motionPath` track moves the node along. */\nexport function setMotionPath<TNode extends CanvasNode>(nodes: readonly TNode[], nodeId: string, motionPath: { d: string; autoRotate?: boolean } | null): TNode[] {\n\tconst draft = new Draft(nodes);\n\tdraft.node(nodeId);\n\tif (motionPath && (typeof motionPath.d !== 'string' || motionPath.d.trim() === '')) {\n\t\tthrow new AnimationEditError('invalid_value', 'A motion path needs SVG path data.');\n\t}\n\tdraft.setMotionPath(nodeId, motionPath ?? undefined);\n\treturn draft.commit();\n}\n\nexport interface KeyframeRangeOptions {\n\t/** Earliest allowed time (default 0). */\n\tmin?: number;\n\t/** Latest allowed time (default unbounded). */\n\tmax?: number;\n}\n\nexport interface MoveKeyframesResult<TNode extends CanvasNode = CanvasNode> {\n\tnodes: TNode[];\n\t/** The delta applied (the request clamped so the whole group stays in range). */\n\tdelta: number;\n\t/** The moved keyframes at their new times, in request order. */\n\trefs: KeyframeRef[];\n}\n\n/**\n * Moves keyframes (across nodes and properties) by one common `delta`, so their relative timing\n * is kept. The delta is clamped so every key stays inside `[min, max]`; a clamped-to-zero move\n * returns the input nodes. Moved keys replace unselected keys at their destination, but never\n * each other: all originals are removed before any is re-inserted. Invalid or duplicate\n * references throw without changing anything.\n */\nexport function moveKeyframes<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\trefs: readonly KeyframeRef[],\n\tdelta: number,\n\toptions: KeyframeRangeOptions = {}\n): MoveKeyframesResult<TNode> {\n\tif (typeof delta !== 'number' || Number.isNaN(delta)) throw new AnimationEditError('invalid_time', 'The delta must be a number.');\n\tconst draft = new Draft(nodes);\n\tvalidateRefs(draft, refs);\n\tconst min = options.min ?? 0;\n\tconst max = options.max ?? Infinity;\n\tlet low = -Infinity;\n\tlet high = Infinity;\n\tfor (const ref of refs) {\n\t\tconst time = draft.keys(ref.nodeId, ref.property)[draft.find(ref)].time;\n\t\tlow = Math.max(low, min - time);\n\t\thigh = Math.min(high, max - time);\n\t}\n\tif (low > high + ANIMATION_TIME_EPSILON) throw new AnimationEditError('no_movement_range', 'The keyframes have no common movement range.');\n\tconst applied = Math.min(high, Math.max(low, delta));\n\tconst moved = refs.map((ref) => ({ ...ref, time: draft.keys(ref.nodeId, ref.property)[draft.find(ref)].time + applied }));\n\tif (Math.abs(applied) <= ANIMATION_TIME_EPSILON) return { nodes: nodes as TNode[], delta: 0, refs: refs.map((r) => ({ ...r })) };\n\tretime(draft, refs, (time) => time + applied);\n\treturn { nodes: draft.commit(), delta: applied, refs: moved };\n}\n\n/** Re-times the referenced keys of each lane: originals out first, then destinations replace. */\nfunction retime(draft: Draft<any>, refs: readonly KeyframeRef[], map: (time: number) => number) {\n\tconst lanes = new Map<string, KeyframeRef[]>();\n\tfor (const ref of refs) lanes.set(refKey(ref), [...(lanes.get(refKey(ref)) ?? []), ref]);\n\tfor (const lane of lanes.values()) {\n\t\tconst { nodeId, property } = lane[0];\n\t\tconst keys = draft.keys(nodeId, property);\n\t\tconst picked = keys.filter((k) => lane.some((r) => sameTime(r.time, k.time)));\n\t\tconst movedKeys = picked.map((k) => ({ ...k, time: map(k.time) }));\n\t\tconst rest = keys.filter((k) => !picked.includes(k) && !movedKeys.some((m) => sameTime(m.time, k.time)));\n\t\tdraft.setKeys(nodeId, property, [...rest, ...movedKeys]);\n\t}\n}\n\nexport interface ScaleKeyframesOptions extends KeyframeRangeOptions {\n\t/** Time that stays fixed (default: the earliest selected keyframe). */\n\tpivot?: number;\n}\n\n/**\n * Scales the spacing of keyframes in time around `pivot` (factor 2 = twice as slow). The factor\n * is reduced so every key stays inside `[min, max]`. Returns the factor applied.\n */\nexport function scaleKeyframes<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\trefs: readonly KeyframeRef[],\n\tfactor: number,\n\toptions: ScaleKeyframesOptions = {}\n): { nodes: TNode[]; factor: number; refs: KeyframeRef[] } {\n\tif (typeof factor !== 'number' || !Number.isFinite(factor) || factor <= 0) throw new AnimationEditError('invalid_factor', 'The scale factor must be a positive number.');\n\tconst draft = new Draft(nodes);\n\tvalidateRefs(draft, refs);\n\tconst times = refs.map((ref) => draft.keys(ref.nodeId, ref.property)[draft.find(ref)].time);\n\tconst pivot = options.pivot ?? Math.min(...times);\n\tconst min = options.min ?? 0;\n\tconst max = options.max ?? Infinity;\n\tlet applied = factor;\n\tfor (const t of times) {\n\t\tif (t > pivot && Number.isFinite(max)) applied = Math.min(applied, (max - pivot) / (t - pivot));\n\t\tif (t < pivot) applied = Math.min(applied, (pivot - min) / (pivot - t));\n\t}\n\tapplied = Math.max(applied, Number.EPSILON);\n\tconst map = (t: number) => pivot + (t - pivot) * applied;\n\tif (Math.abs(applied - 1) <= 1e-12) return { nodes: nodes as TNode[], factor: 1, refs: refs.map((r) => ({ ...r })) };\n\tretime(draft, refs, map);\n\treturn { nodes: draft.commit(), factor: applied, refs: refs.map((r, i) => ({ ...r, time: map(times[i]) })) };\n}\n\n/** Keyframe times on the nodes (all when `ids` is omitted), sorted and unique, minus `exclude`. */\nexport function getKeyframeTimes(nodes: readonly CanvasNode[], options: { ids?: Iterable<string>; exclude?: readonly KeyframeRef[] } = {}): number[] {\n\tconst only = options.ids ? new Set(options.ids) : null;\n\tconst excluded = new Map<string, number[]>();\n\tfor (const ref of options.exclude ?? []) excluded.set(refKey(ref), [...(excluded.get(refKey(ref)) ?? []), ref.time]);\n\tconst times: number[] = [];\n\tfor (const node of nodes) {\n\t\tif (only && !only.has(node.id)) continue;\n\t\tconst animation = getNodeAnimation(node);\n\t\tif (!animation) continue;\n\t\tfor (const [property, track] of Object.entries(animation.tracks)) {\n\t\t\tconst skip = excluded.get(refKey({ nodeId: node.id, property })) ?? [];\n\t\t\tfor (const k of normalizeKeyframes(track?.keyframes)) if (!skip.some((t) => sameTime(t, k.time))) times.push(k.time);\n\t\t}\n\t}\n\ttimes.sort((a, b) => a - b);\n\treturn times.filter((t, i) => i === 0 || !sameTime(t, times[i - 1]));\n}\n\nexport interface KeyframeSnap {\n\t/** The delta after snapping. */\n\tdelta: number;\n\t/** The time snapped to, or `null` when nothing was within the threshold. */\n\ttarget: number | null;\n}\n\n/**\n * Snaps a group move: the selected key (at `times + delta`) nearest to any target pulls the\n * whole group onto it when within `threshold` ms (convert the timeline's screen threshold,\n * e.g. 8px, with its scale). The group's spacing is kept.\n */\nexport function snapKeyframeDelta(times: readonly number[], delta: number, targets: readonly number[], threshold: number): KeyframeSnap {\n\tconst sorted = [...targets].filter(Number.isFinite).sort((a, b) => a - b);\n\tlet best: { distance: number; target: number; delta: number } | null = null;\n\tfor (const time of times) {\n\t\tconst at = time + delta;\n\t\tlet lo = 0;\n\t\tlet hi = sorted.length;\n\t\twhile (lo < hi) {\n\t\t\tconst mid = (lo + hi) >> 1;\n\t\t\tif (sorted[mid] < at) lo = mid + 1;\n\t\t\telse hi = mid;\n\t\t}\n\t\tfor (const i of [lo - 1, lo]) {\n\t\t\tconst target = sorted[i];\n\t\t\tif (target === undefined) continue;\n\t\t\tconst distance = Math.abs(target - at);\n\t\t\tif (distance <= threshold && (!best || distance < best.distance)) best = { distance, target, delta: delta + (target - at) };\n\t\t}\n\t}\n\treturn best ? { delta: best.delta, target: best.target } : { delta, target: null };\n}\n\n// -------------------------------------------------------------------------------------------\n// Copy / paste\n\n/** Clipboard payload for keyframes; times are offsets from the earliest copied keyframe. */\nexport interface AnimationKeyframeClipboard {\n\ttype: 'pixerate/keyframes';\n\tversion: 1;\n\tkeyframes: { nodeId: string; property: string; offset: number; value: AnimationValue; easing?: AnimationEasing }[];\n}\n\n/** Copies keyframes into a clipboard payload. */\nexport function copyKeyframes(nodes: readonly CanvasNode[], refs: readonly KeyframeRef[]): AnimationKeyframeClipboard {\n\tconst draft = new Draft(nodes);\n\tvalidateRefs(draft, refs);\n\tconst found = refs.map((ref) => ({ ref, key: draft.keys(ref.nodeId, ref.property)[draft.find(ref)] }));\n\tconst start = Math.min(...found.map((f) => f.key.time));\n\treturn {\n\t\ttype: 'pixerate/keyframes',\n\t\tversion: 1,\n\t\tkeyframes: found.map(({ ref, key }) => ({\n\t\t\tnodeId: ref.nodeId,\n\t\t\tproperty: ref.property,\n\t\t\toffset: key.time - start,\n\t\t\tvalue: key.value,\n\t\t\t...(key.easing !== undefined ? { easing: key.easing } : {})\n\t\t}))\n\t};\n}\n\n/** Reads a keyframe clipboard payload (object or JSON text), or `null`. */\nexport function parseKeyframeClipboard(input: unknown): AnimationKeyframeClipboard | null {\n\tlet value = input;\n\tif (typeof input === 'string') {\n\t\tif (input.length > 5_000_000) return null;\n\t\ttry {\n\t\t\tvalue = JSON.parse(input);\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\tif (typeof value !== 'object' || value === null) return null;\n\tconst clip = value as Record<string, any>;\n\tif (clip.type !== 'pixerate/keyframes' || clip.version !== 1 || !Array.isArray(clip.keyframes)) return null;\n\tconst keyframes = clip.keyframes.filter(\n\t\t(k: any) =>\n\t\t\tk &&\n\t\t\ttypeof k.nodeId === 'string' &&\n\t\t\ttypeof k.property === 'string' &&\n\t\t\ttypeof k.offset === 'number' &&\n\t\t\tNumber.isFinite(k.offset) &&\n\t\t\t((typeof k.value === 'number' && Number.isFinite(k.value)) || typeof k.value === 'string') &&\n\t\t\t(k.easing === undefined || isAnimationEasing(k.easing))\n\t);\n\treturn keyframes.length > 0 ? { type: 'pixerate/keyframes', version: 1, keyframes } : null;\n}\n\n/**\n * Pastes keyframes with their first key at `time`, replacing keys at the same times. Targets:\n * `nodeIds` (every target gets a single-node clip; several source nodes map onto the targets\n * in order), or the original nodes when omitted. Keys for missing nodes or unknown properties\n * are skipped. Returns the pasted refs.\n */\nexport function pasteKeyframes<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tclipboard: AnimationKeyframeClipboard,\n\ttime: number,\n\toptions: AnimationPropertyOptions & { nodeIds?: readonly string[] } = {}\n): { nodes: TNode[]; refs: KeyframeRef[] } {\n\tconst draft = new Draft(nodes, options);\n\tconst t = coerceTime(time);\n\tconst sources = [...new Set(clipboard.keyframes.map((k) => k.nodeId))];\n\tconst targetsOf = (source: string): string[] => {\n\t\tif (!options.nodeIds || options.nodeIds.length === 0) return [source];\n\t\tif (sources.length === 1) return [...options.nodeIds];\n\t\tconst i = sources.indexOf(source);\n\t\treturn i < options.nodeIds.length ? [options.nodeIds[i]] : [];\n\t};\n\tconst refs: KeyframeRef[] = [];\n\tfor (const k of clipboard.keyframes) {\n\t\tconst p = draft.property(k.property);\n\t\tif (!p) continue;\n\t\tfor (const target of targetsOf(k.nodeId)) {\n\t\t\tif (!draft.has(target)) continue;\n\t\t\tlet value: AnimationValue;\n\t\t\ttry {\n\t\t\t\tvalue = coerceValue(p, k.value);\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst at = t + Math.max(0, k.offset);\n\t\t\tputKey(draft, target, k.property, at, value, k.easing, false);\n\t\t\trefs.push({ nodeId: target, property: k.property, time: at });\n\t\t}\n\t}\n\treturn { nodes: draft.commit(), refs };\n}\n\n// -------------------------------------------------------------------------------------------\n// Recording\n\nconst sameValue = (a: AnimationValue | undefined, b: AnimationValue | undefined) =>\n\ttypeof a === 'number' && typeof b === 'number' ? Math.abs(a - b) <= 1e-9 : a === b;\n\nexport interface RecordAnimationOptions extends AnimationPropertyOptions {\n\t/**\n\t * Record mode: changing a property that is not animated yet keys it too (otherwise only\n\t * properties that already have keyframes are keyed and other changes are static edits).\n\t */\n\trecording?: boolean;\n}\n\n/**\n * Turns edits made on the *displayed* (evaluated) nodes into edits of the stored nodes.\n *\n * - `base`: the stored nodes; `before`: what was displayed (`applyAnimation(base, time)`);\n * `after`: the displayed nodes after the edit (e.g. React Flow changes applied to `before`).\n * - A changed property with an enabled track is keyed at `time` and keeps its stored value.\n * - In record mode a changed property without a track gets one: a key at 0 with the previous\n * value (when `time` > 0) and a key at `time`, so it animates from where it was. The stored\n * value stays the previous one.\n * - Everything else (other fields, unanimated properties outside record mode) passes through.\n *\n * Unchanged nodes keep their stored identity; returns the new stored nodes and the keyed refs.\n */\nexport function recordAnimationChanges<TNode extends CanvasNode>(\n\tbase: readonly TNode[],\n\tbefore: readonly TNode[],\n\tafter: readonly TNode[],\n\ttime: number,\n\toptions: RecordAnimationOptions = {}\n): { nodes: TNode[]; keyed: KeyframeRef[] } {\n\tconst properties = (options.properties ?? ANIMATABLE_PROPERTIES).filter((p) => p.key !== 'motionPath');\n\tconst baseById = new Map(base.map((n) => [n.id, n]));\n\tconst beforeById = new Map(before.map((n) => [n.id, n]));\n\tconst t = coerceTime(time);\n\tconst keyed: KeyframeRef[] = [];\n\tconst out = after.map((next) => {\n\t\tconst stored = baseById.get(next.id);\n\t\tconst shown = beforeById.get(next.id);\n\t\tif (!stored) return next;\n\t\tif (shown === next) return stored;\n\t\tconst animation = getNodeAnimation(stored);\n\t\tlet result: CanvasNode = next;\n\t\tlet tracks: Record<string, AnimationTrack> | null = null;\n\t\tconst tracksOf = () => (tracks ??= { ...(animation?.tracks ?? {}) });\n\t\tfor (const p of properties) {\n\t\t\tconst track = getAnimationTrack(stored, p.key);\n\t\t\tconst animated = !!track && track.enabled !== false;\n\t\t\tconst now = p.read(next);\n\t\t\tconst was = shown ? p.read(shown) : undefined;\n\t\t\tconst changed = !!shown && now !== undefined && !sameValue(now, was);\n\t\t\tconst restore = () => {\n\t\t\t\tconst value = p.read(stored);\n\t\t\t\tif (value !== undefined) result = p.write(result, value);\n\t\t\t};\n\t\t\tif (animated) {\n\t\t\t\tif (changed) {\n\t\t\t\t\tconst keys = normalizeKeyframes(track!.keyframes).filter((k) => !sameTime(k.time, t));\n\t\t\t\t\tconst before = [...keys].reverse().find((k) => k.time < t);\n\t\t\t\t\tkeys.push({ time: t, value: now!, ...(before?.easing !== undefined ? { easing: before.easing } : {}) });\n\t\t\t\t\ttracksOf()[p.key] = { ...track!, keyframes: keys.sort((a, b) => a.time - b.time) };\n\t\t\t\t\tkeyed.push({ nodeId: next.id, property: p.key, time: t });\n\t\t\t\t}\n\t\t\t\trestore();\n\t\t\t} else if (changed && options.recording && !track) {\n\t\t\t\tconst keys: AnimationKeyframe[] = [];\n\t\t\t\tif (t > ANIMATION_TIME_EPSILON && was !== undefined) keys.push({ time: 0, value: was });\n\t\t\t\tkeys.push({ time: t, value: now! });\n\t\t\t\ttracksOf()[p.key] = { keyframes: keys };\n\t\t\t\tkeyed.push({ nodeId: next.id, property: p.key, time: t });\n\t\t\t\trestore();\n\t\t\t}\n\t\t}\n\t\tif (tracks) result = withNodeAnimation(result, { ...(animation ?? {}), tracks });\n\t\treturn result as TNode;\n\t});\n\treturn { nodes: out, keyed };\n}\n\n// -------------------------------------------------------------------------------------------\n// One-shot animations (agents, scripts)\n\nexport interface AnimateNodeOptions extends AnimationPropertyOptions {\n\t/** Values at the end (property → value). */\n\tto: Record<string, AnimationValue>;\n\t/** Values at the start (default: what the node shows at `delay`). */\n\tfrom?: Record<string, AnimationValue>;\n\t/** Duration in ms (default 1000). */\n\tduration?: number;\n\t/** Start time in ms (default 0). */\n\tdelay?: number;\n\teasing?: AnimationEasing | string;\n}\n\n/**\n * Animates a node from `from` (or its current values) to `to` over `duration` ms starting at\n * `delay`: two keyframes per property, replacing keys at those times.\n */\nexport function animateNode<TNode extends CanvasNode>(nodes: readonly TNode[], nodeId: string, options: AnimateNodeOptions): { nodes: TNode[]; refs: KeyframeRef[] } {\n\tconst draft = new Draft(nodes, options);\n\tconst node = draft.node(nodeId);\n\tconst start = coerceTime(options.delay ?? 0);\n\tconst duration = options.duration ?? 1000;\n\tif (typeof duration !== 'number' || !Number.isFinite(duration) || duration <= 0) throw new AnimationEditError('invalid_time', 'The duration must be positive.');\n\tconst easing = options.easing === undefined ? undefined : coerceEasing(options.easing);\n\tconst entries = Object.entries(options.to ?? {});\n\tif (entries.length === 0) throw new AnimationEditError('invalid_value', 'Name at least one property to animate.');\n\tconst refs: KeyframeRef[] = [];\n\tconst writes: [string, AnimationValue, AnimationValue][] = [];\n\tfor (const [key, target] of entries) {\n\t\tconst p = draft.property(key);\n\t\tif (!p || (key === 'motionPath' && !getNodeAnimation(node)?.motionPath)) {\n\t\t\tthrow new AnimationEditError('unknown_property', `\"${key}\" cannot be animated${p ? ' without a motion path' : ''}.`);\n\t\t}\n\t\tconst from = options.from?.[key] ?? (key === 'motionPath' ? 0 : getAnimatedValue(node, key, start, options));\n\t\tif (from === undefined) throw new AnimationEditError('invalid_value', `\"${nodeId}\" has no ${key} to animate from; pass \"from\".`);\n\t\twrites.push([key, coerceValue(p, from), coerceValue(p, target)]);\n\t}\n\tfor (const [key, from, to] of writes) {\n\t\tputKey(draft, nodeId, key, start, from, easing);\n\t\tputKey(draft, nodeId, key, start + duration, to, undefined, false);\n\t\trefs.push({ nodeId, property: key, time: start }, { nodeId, property: key, time: start + duration });\n\t}\n\treturn { nodes: draft.commit(), refs };\n}\n","import type { CanvasNode } from '../types.js';\nimport { easingToCss } from './easing.js';\nimport { evaluateAnimation, evaluateTrack, getAnimationDuration, type AnimationDocument, type AnimationLoopMode, type AnimationOverrides, type EvaluateAnimationOptions } from './evaluate.js';\nimport { ANIMATABLE_PROPERTIES, getNodeAnimation, normalizeKeyframes, type AnimationKeyframe, type AnimationValue } from './model.js';\n\nconst nodesOf = (doc: AnimationDocument): readonly CanvasNode[] => (Array.isArray(doc) ? doc : (doc as { nodes: readonly CanvasNode[] }).nodes);\n\nexport interface SampleAnimationOptions extends EvaluateAnimationOptions {\n\t/** Frames per second (default 30). */\n\tfps?: number;\n\t/** Length in ms (default: the last keyframe time). */\n\tduration?: number;\n\t/** First time sampled (default 0). */\n\tstart?: number;\n\t/** Refuses to produce more frames than this (default 100 000). */\n\tmaxFrames?: number;\n}\n\nexport interface AnimationFrame {\n\tindex: number;\n\t/** Time in ms. */\n\ttime: number;\n\toverrides: AnimationOverrides;\n}\n\n/**\n * Samples the animation at a fixed frame rate from `start` through `start + duration`\n * (both included), e.g. to render video frames. Apply a frame with `applyAnimationOverrides`.\n */\nexport function sampleAnimationFrames(doc: AnimationDocument, options: SampleAnimationOptions = {}): AnimationFrame[] {\n\tconst fps = options.fps ?? 30;\n\tif (!(fps > 0) || !Number.isFinite(fps)) throw new RangeError('fps must be a positive number.');\n\tconst start = options.start ?? 0;\n\tconst duration = Math.max(0, options.duration ?? getAnimationDuration(doc) - start);\n\tconst step = 1000 / fps;\n\tconst count = Math.floor(duration / step + 1e-9) + 1;\n\tif (count > (options.maxFrames ?? 100_000)) throw new RangeError(`Sampling would produce ${count} frames.`);\n\tconst frames: AnimationFrame[] = [];\n\tfor (let i = 0; i < count; i++) {\n\t\tconst time = start + i * step;\n\t\tframes.push({ index: i, time, overrides: evaluateAnimation(doc, time, options) });\n\t}\n\treturn frames;\n}\n\n// -------------------------------------------------------------------------------------------\n// CSS / Web Animations\n\n/** CSS property each animatable property maps to (camelCase for the Web Animations API). */\nexport const CSS_ANIMATION_PROPERTIES: Readonly<Record<string, { css: string; camel: string }>> = {\n\tx: { css: 'translate', camel: 'translate' },\n\ty: { css: 'translate', camel: 'translate' },\n\tmotionPath: { css: 'translate', camel: 'translate' },\n\trotation: { css: 'rotate', camel: 'rotate' },\n\tscale: { css: 'scale', camel: 'scale' },\n\topacity: { css: 'opacity', camel: 'opacity' },\n\tfill: { css: 'background-color', camel: 'backgroundColor' },\n\tstroke: { css: 'border-color', camel: 'borderColor' },\n\tstrokeWidth: { css: 'border-width', camel: 'borderWidth' },\n\tcornerRadius: { css: 'border-radius', camel: 'borderRadius' },\n\twidth: { css: 'width', camel: 'width' },\n\theight: { css: 'height', camel: 'height' }\n};\n\nexport interface AnimationStop {\n\t/** 0–1 */\n\toffset: number;\n\tvalue: string;\n\t/** Timing function of the segment starting here. */\n\teasing?: string;\n}\n\nexport interface CssPropertyAnimation {\n\t/** CSS property, e.g. `translate`. */\n\tproperty: string;\n\t/** camelCase name for `element.animate`. */\n\tcamel: string;\n\t/** Animatable properties driving it, e.g. `['x', 'y']`. */\n\tsources: string[];\n\tstops: AnimationStop[];\n\t/** False when the stops are sampled (colours mixed in OKLab, x / y with different timing, motion paths). */\n\texact: boolean;\n}\n\nexport interface CssExportOptions {\n\t/** Timeline length in ms (default: the document's last keyframe time). */\n\tduration?: number;\n\tloop?: AnimationLoopMode;\n\tplaybackRate?: number;\n\t/** Sampling rate for tracks CSS cannot express exactly (default 30 per second). */\n\tsampleFps?: number;\n\t/** Map properties to other CSS properties (e.g. `{ fill: { css: 'fill', camel: 'fill' } }` for SVG). */\n\tcssProperties?: Record<string, { css: string; camel: string }>;\n}\n\nconst round = (v: number) => Math.round(v * 1000) / 1000;\n\nfunction cssValue(source: string, value: AnimationValue, node: CanvasNode, partner?: AnimationValue, partnerSource?: string): string {\n\tif (source === 'x' || source === 'y') {\n\t\tconst dx = source === 'x' ? (value as number) - node.position.x : partner !== undefined ? (partner as number) - node.position.x : 0;\n\t\tconst dy = source === 'y' ? (value as number) - node.position.y : partner !== undefined && partnerSource === 'y' ? (partner as number) - node.position.y : 0;\n\t\treturn `${round(dx)}px ${round(dy)}px`;\n\t}\n\tif (typeof value === 'string') return value;\n\tswitch (source) {\n\t\tcase 'rotation':\n\t\t\treturn `${round(value)}deg`;\n\t\tcase 'scale':\n\t\tcase 'opacity':\n\t\t\treturn String(round(value));\n\t\tdefault:\n\t\t\treturn `${round(value)}px`;\n\t}\n}\n\nconst sameTimes = (a: AnimationKeyframe[], b: AnimationKeyframe[]) =>\n\ta.length === b.length && a.every((k, i) => Math.abs(k.time - b[i].time) < 1e-6 && JSON.stringify(k.easing ?? 'linear') === JSON.stringify(b[i].easing ?? 'linear'));\n\nfunction needsOklab(keys: AnimationKeyframe[]): boolean {\n\tfor (let i = 0; i + 1 < keys.length; i++) {\n\t\tif (keys[i].easing !== 'hold' && keys[i].value !== keys[i + 1].value) return true;\n\t}\n\treturn false;\n}\n\nfunction exactStops(keys: AnimationKeyframe[], duration: number, format: (k: AnimationKeyframe, i: number) => string): AnimationStop[] {\n\tconst stops: AnimationStop[] = [];\n\tif (keys[0].time > 0) stops.push({ offset: 0, value: format(keys[0], 0), easing: 'linear' });\n\tkeys.forEach((k, i) => {\n\t\tconst next = keys[i + 1];\n\t\tstops.push({\n\t\t\toffset: Math.min(1, Math.max(0, k.time / duration)),\n\t\t\tvalue: format(k, i),\n\t\t\t...(next ? { easing: easingToCss(k.easing, next.time - k.time) } : {})\n\t\t});\n\t});\n\tconst last = keys[keys.length - 1];\n\tif (last.time < duration) stops.push({ offset: 1, value: format(last, keys.length - 1) });\n\treturn stops;\n}\n\nfunction sampledStops(duration: number, fps: number, keyTimes: number[], valueAt: (time: number) => string): AnimationStop[] {\n\tconst times = new Set<number>([0, duration, ...keyTimes.filter((t) => t >= 0 && t <= duration)]);\n\tconst step = 1000 / Math.max(1, fps);\n\tfor (let t = step; t < duration; t += step) times.add(t);\n\treturn [...times]\n\t\t.sort((a, b) => a - b)\n\t\t.map((t) => ({ offset: Math.min(1, t / duration), value: valueAt(t), easing: 'linear' }));\n}\n\n/**\n * The node's enabled tracks as per-CSS-property keyframe stops: rotation / scale use the\n * individual transform properties, x / y become `translate` relative to the node's position.\n * Tracks CSS cannot reproduce exactly are sampled (`exact: false`).\n */\nexport function getCssPropertyAnimations(nodes: readonly CanvasNode[], nodeId: string, options: CssExportOptions = {}): CssPropertyAnimation[] {\n\tconst node = nodes.find((n) => n.id === nodeId);\n\tconst animation = getNodeAnimation(node);\n\tif (!node || !animation) return [];\n\tconst duration = options.duration ?? getAnimationDuration([node]);\n\tif (!(duration > 0)) return [];\n\tconst fps = options.sampleFps ?? 30;\n\tconst map = { ...CSS_ANIMATION_PROPERTIES, ...(options.cssProperties ?? {}) };\n\tconst tracks = new Map<string, AnimationKeyframe[]>();\n\tfor (const [key, track] of Object.entries(animation.tracks)) {\n\t\tif (!track || track.enabled === false || !map[key]) continue;\n\t\tconst keys = normalizeKeyframes(track.keyframes);\n\t\tif (keys.length > 0) tracks.set(key, keys);\n\t}\n\tconst typeOf = (key: string) => ANIMATABLE_PROPERTIES.find((p) => p.key === key)?.type ?? 'number';\n\tconst groups = new Map<string, string[]>();\n\tfor (const key of tracks.keys()) {\n\t\tconst css = map[key].css;\n\t\tgroups.set(css, [...(groups.get(css) ?? []), key]);\n\t}\n\tconst out: CssPropertyAnimation[] = [];\n\tfor (const [css, sources] of groups) {\n\t\tconst camel = map[sources[0]].camel;\n\t\tconst keysOf = (k: string) => tracks.get(k)!;\n\t\tconst evalAt = (k: string, t: number) => evaluateTrack(keysOf(k), t, typeOf(k))!;\n\t\tconst allTimes = sources.flatMap((k) => keysOf(k).map((x) => x.time));\n\t\tif (sources.includes('motionPath')) {\n\t\t\t// The motion path track drives the translate; sample its positions.\n\t\t\tconst position = (t: number) => {\n\t\t\t\tconst moved = ANIMATABLE_PROPERTIES.find((p) => p.key === 'motionPath')!.write(node, evalAt('motionPath', t));\n\t\t\t\treturn `${round(moved.position.x - node.position.x)}px ${round(moved.position.y - node.position.y)}px`;\n\t\t\t};\n\t\t\tout.push({ property: css, camel, sources, stops: sampledStops(duration, fps, allTimes, position), exact: false });\n\t\t\tcontinue;\n\t\t}\n\t\tif (sources.length === 2 && css === 'translate') {\n\t\t\tconst [a, b] = sources[0] === 'x' ? sources : [sources[1], sources[0]];\n\t\t\tif (sameTimes(keysOf(a), keysOf(b))) {\n\t\t\t\tconst bKeys = keysOf(b);\n\t\t\t\tout.push({ property: css, camel, sources, stops: exactStops(keysOf(a), duration, (k, i) => cssValue(a, k.value, node, bKeys[i].value, b)), exact: true });\n\t\t\t} else {\n\t\t\t\tout.push({\n\t\t\t\t\tproperty: css,\n\t\t\t\t\tcamel,\n\t\t\t\t\tsources,\n\t\t\t\t\tstops: sampledStops(duration, fps, allTimes, (t) => cssValue(a, evalAt(a, t), node, evalAt(b, t), b)),\n\t\t\t\t\texact: false\n\t\t\t\t});\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tconst key = sources[sources.length - 1];\n\t\tconst keys = keysOf(key);\n\t\tif (typeOf(key) === 'color' && needsOklab(keys)) {\n\t\t\tout.push({ property: css, camel, sources: [key], stops: sampledStops(duration, fps, allTimes, (t) => cssValue(key, evalAt(key, t), node)), exact: false });\n\t\t} else {\n\t\t\tout.push({ property: css, camel, sources: [key], stops: exactStops(keys, duration, (k) => cssValue(key, k.value, node)), exact: true });\n\t\t}\n\t}\n\treturn out;\n}\n\nfunction timing(options: CssExportOptions, duration: number) {\n\tconst rate = options.playbackRate && Number.isFinite(options.playbackRate) && options.playbackRate !== 0 ? options.playbackRate : 1;\n\tconst loop = options.loop ?? 'none';\n\treturn {\n\t\tduration: round(duration / Math.abs(rate)),\n\t\titerations: loop === 'none' ? 1 : Infinity,\n\t\tdirection: (loop === 'ping-pong' ? (rate < 0 ? 'alternate-reverse' : 'alternate') : rate < 0 ? 'reverse' : 'normal') as PlaybackDirectionLike\n\t};\n}\n\ntype PlaybackDirectionLike = 'normal' | 'reverse' | 'alternate' | 'alternate-reverse';\n\nexport interface CssAnimationExport {\n\t/** `@keyframes` blocks plus a rule applying them to `selector`. */\n\tcss: string;\n\t/** Value for the `animation` property (comma-separated, one per CSS property). */\n\tanimation: string;\n\tanimations: { name: string; property: string; exact: boolean }[];\n\t/** Whether every property is reproduced exactly. */\n\texact: boolean;\n}\n\nconst cssIdent = (text: string) => text.replace(/[^A-Za-z0-9_-]/g, '-').replace(/^(\\d)/, '_$1');\n\n/**\n * Exports a node's animation as CSS `@keyframes` (one per CSS property, so different tracks\n * keep their own easing) and an `animation` declaration for `selector`.\n */\nexport function exportCssAnimation(\n\tnodes: readonly CanvasNode[],\n\tnodeId: string,\n\toptions: CssExportOptions & { name?: string; selector?: string } = {}\n): CssAnimationExport {\n\tconst duration = options.duration ?? getAnimationDuration(nodes.filter((n) => n.id === nodeId));\n\tconst props = getCssPropertyAnimations(nodes, nodeId, { ...options, duration });\n\tconst base = cssIdent(options.name ?? `anim-${nodeId}`);\n\tconst t = timing(options, duration);\n\tconst blocks: string[] = [];\n\tconst parts: string[] = [];\n\tconst animations: CssAnimationExport['animations'] = [];\n\tfor (const prop of props) {\n\t\tconst name = `${base}-${cssIdent(prop.property)}`;\n\t\tconst lines = prop.stops.map((s) => {\n\t\t\tconst pct = `${round(s.offset * 100)}%`;\n\t\t\tconst easing = s.easing ? ` animation-timing-function: ${s.easing};` : '';\n\t\t\treturn ` ${pct} { ${prop.property}: ${s.value};${easing} }`;\n\t\t});\n\t\tblocks.push(`@keyframes ${name} {\\n${lines.join('\\n')}\\n}`);\n\t\tparts.push(`${name} ${t.duration}ms linear 0ms ${t.iterations === Infinity ? 'infinite' : 1} ${t.direction} both`);\n\t\tanimations.push({ name, property: prop.property, exact: prop.exact });\n\t}\n\tconst animation = parts.join(', ');\n\tconst selector = options.selector ?? `[data-id=\"${nodeId.replace(/[\"\\\\]/g, '\\\\$&')}\"]`;\n\tconst css = blocks.length ? `${blocks.join('\\n\\n')}\\n\\n${selector} {\\n animation: ${animation};\\n}\\n` : '';\n\treturn { css, animation, animations, exact: props.every((p) => p.exact) };\n}\n\n/** One `element.animate(keyframes, options)` call. */\nexport interface WebAnimationSpec {\n\tproperty: string;\n\tkeyframes: { offset: number; easing?: string; [property: string]: string | number | undefined }[];\n\toptions: { duration: number; iterations: number; direction: PlaybackDirectionLike; fill: 'both'; easing: 'linear' };\n\texact: boolean;\n}\n\n/** The node's animation as Web Animations API keyframes (one animation per CSS property). */\nexport function toWebAnimations(nodes: readonly CanvasNode[], nodeId: string, options: CssExportOptions = {}): WebAnimationSpec[] {\n\tconst duration = options.duration ?? getAnimationDuration(nodes.filter((n) => n.id === nodeId));\n\tconst t = timing(options, duration);\n\treturn getCssPropertyAnimations(nodes, nodeId, { ...options, duration }).map((prop) => ({\n\t\tproperty: prop.property,\n\t\tkeyframes: prop.stops.map((s) => ({ offset: s.offset, [prop.camel]: s.value, ...(s.easing ? { easing: s.easing } : {}) })),\n\t\toptions: { duration: t.duration, iterations: t.iterations, direction: t.direction, fill: 'both', easing: 'linear' },\n\t\texact: prop.exact\n\t}));\n}\n\n/** Plays Web Animations specs on an element (anything with `animate`), returning the animations. */\nexport function playWebAnimations<TAnimation>(\n\telement: { animate(keyframes: any, options: any): TAnimation },\n\tspecs: readonly WebAnimationSpec[]\n): TAnimation[] {\n\treturn specs.map((spec) => element.animate(spec.keyframes, spec.options));\n}\n\n/** Whether every enabled track of the node can be exported without sampling. */\nexport function isCssExportExact(nodes: readonly CanvasNode[], nodeId: string): boolean {\n\treturn getCssPropertyAnimations(nodes, nodeId).every((p) => p.exact);\n}\n\n","import type { CanvasNode } from '../types.js';\nimport type { AnimationEasing } from './easing.js';\nimport type { KeyframeRef } from './edit.js';\nimport {\n\tANIMATABLE_PROPERTIES,\n\tANIMATION_TIME_EPSILON,\n\tgetAnimatedProperties,\n\tgetAnimationTrack,\n\tnormalizeKeyframes,\n\ttype AnimationPropertyOptions,\n\ttype AnimationValue\n} from './model.js';\n\n/**\n * View-model helpers for timeline UIs (React and Svelte render the same rows).\n */\n\nexport interface TimelineKeyframe {\n\tref: KeyframeRef;\n\t/** Time shown (includes the preview offset of a keyframe drag). */\n\ttime: number;\n\tvalue: AnimationValue;\n\teasing?: AnimationEasing;\n\tselected: boolean;\n}\n\nexport interface TimelineRow {\n\t/** `nodeId:property`. */\n\tid: string;\n\tnodeId: string;\n\tnodeLabel: string;\n\tproperty: string;\n\tlabel: string;\n\tenabled: boolean;\n\tkeyframes: TimelineKeyframe[];\n}\n\nexport interface TimelineRowsOptions extends AnimationPropertyOptions {\n\t/** Selected keyframes. */\n\tselection?: readonly KeyframeRef[];\n\t/** Preview offset for the selected keyframes while they are dragged. */\n\tdragDelta?: number;\n\t/** Row label for a node (default: `data.label`, then the id). */\n\tlabelOf?: (node: CanvasNode) => string;\n}\n\n/** Whether `ref` is in `selection` (same node, property and time). */\nexport function isKeyframeSelected(selection: readonly KeyframeRef[], ref: KeyframeRef): boolean {\n\treturn selection.some((s) => s.nodeId === ref.nodeId && s.property === ref.property && Math.abs(s.time - ref.time) <= ANIMATION_TIME_EPSILON);\n}\n\nconst defaultLabel = (node: CanvasNode) => (typeof node.data?.label === 'string' && node.data.label.trim() ? node.data.label : node.id);\n\n/** One row per animated property of each node in `ids` (in `ids` order). */\nexport function getTimelineRows(nodes: readonly CanvasNode[], ids: Iterable<string>, options: TimelineRowsOptions = {}): TimelineRow[] {\n\tconst byId = new Map(nodes.map((n) => [n.id, n]));\n\tconst selection = options.selection ?? [];\n\tconst labelOf = options.labelOf ?? defaultLabel;\n\tconst rows: TimelineRow[] = [];\n\tfor (const id of new Set(ids)) {\n\t\tconst node = byId.get(id);\n\t\tif (!node) continue;\n\t\tfor (const property of getAnimatedProperties(node, options)) {\n\t\t\tconst track = getAnimationTrack(node, property)!;\n\t\t\tconst label = (options.properties ?? ANIMATABLE_PROPERTIES).find((p) => p.key === property)?.label ?? property;\n\t\t\trows.push({\n\t\t\t\tid: `${id}:${property}`,\n\t\t\t\tnodeId: id,\n\t\t\t\tnodeLabel: labelOf(node),\n\t\t\t\tproperty,\n\t\t\t\tlabel,\n\t\t\t\tenabled: track.enabled !== false,\n\t\t\t\tkeyframes: normalizeKeyframes(track.keyframes).map((k) => {\n\t\t\t\t\tconst ref = { nodeId: id, property, time: k.time };\n\t\t\t\t\tconst selected = isKeyframeSelected(selection, ref);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tref,\n\t\t\t\t\t\ttime: selected && options.dragDelta ? k.time + options.dragDelta : k.time,\n\t\t\t\t\t\tvalue: k.value,\n\t\t\t\t\t\t...(k.easing !== undefined ? { easing: k.easing } : {}),\n\t\t\t\t\t\tselected\n\t\t\t\t\t};\n\t\t\t\t})\n\t\t\t});\n\t\t}\n\t}\n\treturn rows;\n}\n\n/** x position (px) of `time` on a lane `width` px wide showing `[0, duration]`. */\nexport function timeToX(time: number, duration: number, width: number): number {\n\treturn duration > 0 ? (time / duration) * width : 0;\n}\n\n/** Time at x (px) on a lane `width` px wide, clamped to `[0, duration]`. */\nexport function xToTime(x: number, duration: number, width: number): number {\n\tif (!(width > 0) || !(duration > 0)) return 0;\n\treturn Math.min(duration, Math.max(0, (x / width) * duration));\n}\n\n/** `1.25s` style label. */\nexport function formatAnimationTime(ms: number): string {\n\tconst s = Math.max(0, ms) / 1000;\n\treturn `${s.toFixed(s < 10 ? 2 : 1)}s`;\n}\n\nconst STEPS = [10, 20, 50, 100, 200, 250, 500, 1000, 2000, 5000, 10_000, 15_000, 30_000, 60_000, 120_000, 300_000, 600_000];\n\n/** Ruler ticks at a round step leaving at least `minSpacing` px between labels. */\nexport function getTimelineTicks(duration: number, width: number, minSpacing = 60): { time: number; x: number; label: string }[] {\n\tif (!(duration > 0) || !(width > 0)) return [];\n\tconst step = STEPS.find((s) => timeToX(s, duration, width) >= minSpacing) ?? STEPS[STEPS.length - 1];\n\tconst ticks: { time: number; x: number; label: string }[] = [];\n\tfor (let t = 0; t <= duration + 1e-6 && ticks.length < 1000; t += step) {\n\t\tticks.push({ time: t, x: timeToX(t, duration, width), label: formatAnimationTime(t) });\n\t}\n\treturn ticks;\n}\n","// Portions ported from filmcraft-main/crates/engine/src/keyframe_move.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport type { Command, MergedExecutionOptions } from '../../history/types.js';\nimport type { Clock } from '../clock.js';\nimport type { CanvasNode } from '../types.js';\nimport type { AnimationEasing } from './easing.js';\nimport {\n\taddKeyframesAtTime,\n\tanimateNode,\n\tcopyKeyframes,\n\tdeleteKeyframes,\n\tgetKeyframeTimes,\n\tmoveKeyframes,\n\tpasteKeyframes,\n\trecordAnimationChanges,\n\tremoveTrack,\n\tscaleKeyframes,\n\tsetKeyframe,\n\tsetKeyframeEasing,\n\tsetMotionPath,\n\tsetTrackEnabled,\n\tsnapKeyframeDelta,\n\tsnapTimeToFrame,\n\tupdateKeyframe,\n\ttype AnimateNodeOptions,\n\ttype AnimationKeyframeClipboard,\n\ttype KeyframePatch,\n\ttype KeyframeRef\n} from './edit.js';\nimport { applyAnimation, getAnimationDuration, type AnimationLoopMode } from './evaluate.js';\nimport { exportCssAnimation, sampleAnimationFrames, toWebAnimations, type CssExportOptions, type SampleAnimationOptions } from './export.js';\nimport { ANIMATION_DATA_KEY, ANIMATION_TIME_EPSILON, getNodeAnimation, normalizeKeyframes, type AnimatableProperty, type AnimationValue } from './model.js';\nimport { AnimationPlayer, type AnimationPlayerState } from './player.js';\nimport { getTimelineRows, isKeyframeSelected, type TimelineRow, type TimelineRowsOptions } from './timeline.js';\n\n/** History surface (`HistoryManager` satisfies it). */\nexport interface AnimationHistoryTarget {\n\texecute(command: Command): void;\n\texecuteMerged?(command: Command, options?: MergedExecutionOptions): void;\n}\n\nexport interface KeyframeDragState {\n\trefs: KeyframeRef[];\n\t/** Offset previewed (after clamping and snapping). */\n\tdelta: number;\n\t/** Time snapped to, for a snap guide. */\n\ttarget: number | null;\n}\n\nexport interface CanvasAnimationState extends AnimationPlayerState {\n\t/** Record mode: changing an unanimated property at the playhead keys it. */\n\trecording: boolean;\n\t/** Frame rate used for frame snapping and export (default 30). */\n\tfps: number;\n\tselection: readonly KeyframeRef[];\n\tdrag: KeyframeDragState | null;\n\t/** Whether the duration follows the last keyframe (`setDuration(null)`). */\n\tautoDuration: boolean;\n}\n\nexport interface CanvasAnimationControllerOptions<TNode extends CanvasNode = CanvasNode> {\n\tgetNodes: () => TNode[];\n\tsetNodes: (nodes: TNode[]) => void;\n\t/** When set, keyframe edits are undoable steps (recording merges a drag into one step). */\n\thistory?: AnimationHistoryTarget;\n\t/** Ids acted on without explicit ids (default: nodes with `selected: true`). */\n\tgetSelectedIds?: () => string[];\n\t/** Time source for playback (default `realClock`). */\n\tclock?: Clock;\n\t/** Fixed timeline length in ms; omitted = the last keyframe time, at least `minDuration`. */\n\tduration?: number;\n\t/** Shortest automatic duration (default 1000 ms). */\n\tminDuration?: number;\n\tloop?: AnimationLoopMode;\n\tplaybackRate?: number;\n\tfps?: number;\n\trecording?: boolean;\n\tproperties?: readonly AnimatableProperty[];\n\t/** Called when the playhead moves. */\n\tonTime?: (time: number) => void;\n\tonChange?: (state: CanvasAnimationState) => void;\n}\n\nconst uniq = <T>(items: Iterable<T>) => [...new Set(items)];\n\n/**\n * Framework-agnostic animation controller shared by the React hook and the Svelte rune:\n * playback (on an injected clock), record mode, keyframe editing with undo, multi-keyframe\n * drag with snapping, copy / paste and export.\n *\n * Render `display()` (the nodes evaluated at the playhead) and route edits made on the\n * displayed nodes through `applyDisplayChanges`, so animated properties are keyed instead of\n * being overwritten by the next frame.\n */\nexport function createCanvasAnimationController<TNode extends CanvasNode = CanvasNode>(options: CanvasAnimationControllerOptions<TNode>) {\n\tlet fixedDuration: number | null = options.duration !== undefined && options.duration > 0 ? options.duration : null;\n\tlet recording = options.recording ?? false;\n\tlet fps = options.fps && options.fps > 0 ? options.fps : 30;\n\tlet selection: KeyframeRef[] = [];\n\tlet drag: KeyframeDragState | null = null;\n\tlet clipboard: AnimationKeyframeClipboard | null = null;\n\tconst listeners = new Set<(state: CanvasAnimationState) => void>();\n\tconst propertyOptions = () => (options.properties ? { properties: options.properties } : {});\n\n\tconst durationOf = (nodes: readonly CanvasNode[]) =>\n\t\tfixedDuration ?? Math.max(options.minDuration ?? 1000, getAnimationDuration(nodes));\n\n\tlet durationNodes: readonly CanvasNode[] | null = null;\n\tlet durationValue = 0;\n\tconst duration = () => {\n\t\tconst nodes = options.getNodes();\n\t\tif (nodes !== durationNodes) {\n\t\t\tdurationNodes = nodes;\n\t\t\tdurationValue = durationOf(nodes);\n\t\t}\n\t\treturn fixedDuration ?? durationValue;\n\t};\n\n\tconst player = new AnimationPlayer({\n\t\tclock: options.clock,\n\t\tduration,\n\t\tloop: options.loop,\n\t\tplaybackRate: options.playbackRate,\n\t\tonTime: (time) => options.onTime?.(time)\n\t});\n\n\tlet cached: CanvasAnimationState | null = null;\n\tconst snapshot = (): CanvasAnimationState => ({\n\t\t...player.getState(),\n\t\tduration: duration(),\n\t\trecording,\n\t\tfps,\n\t\tselection,\n\t\tdrag,\n\t\tautoDuration: fixedDuration === null\n\t});\n\tconst getState = (): CanvasAnimationState => {\n\t\tconst next = snapshot();\n\t\tif (\n\t\t\tcached &&\n\t\t\tcached.time === next.time &&\n\t\t\tcached.playing === next.playing &&\n\t\t\tcached.scrubbing === next.scrubbing &&\n\t\t\tcached.loop === next.loop &&\n\t\t\tcached.playbackRate === next.playbackRate &&\n\t\t\tcached.duration === next.duration &&\n\t\t\tcached.recording === next.recording &&\n\t\t\tcached.fps === next.fps &&\n\t\t\tcached.selection === next.selection &&\n\t\t\tcached.drag === next.drag &&\n\t\t\tcached.autoDuration === next.autoDuration\n\t\t) {\n\t\t\treturn cached;\n\t\t}\n\t\tcached = next;\n\t\treturn cached;\n\t};\n\tconst notify = () => {\n\t\tconst state = getState();\n\t\toptions.onChange?.(state);\n\t\tfor (const listener of listeners) listener(state);\n\t};\n\tplayer.subscribe(notify);\n\n\tconst selectedIds = () => options.getSelectedIds?.() ?? options.getNodes().filter((n) => n.selected).map((n) => n.id);\n\n\t// ----- applying edits -----------------------------------------------------------------\n\n\tconst KEEP_FIELDS = ['selected', 'dragging', 'measured'] as const;\n\tconst replaceNodes = (nodes: TNode[], replacements: Map<string, TNode>, animationOnly = false): TNode[] =>\n\t\tnodes.map((node) => {\n\t\t\tconst replacement = replacements.get(node.id);\n\t\t\tif (!replacement) return node;\n\t\t\tif (animationOnly) {\n\t\t\t\tconst data: Record<string, any> = { ...(node.data ?? {}) };\n\t\t\t\tconst value = replacement.data?.[ANIMATION_DATA_KEY];\n\t\t\t\tif (value === undefined) delete data[ANIMATION_DATA_KEY];\n\t\t\t\telse data[ANIMATION_DATA_KEY] = value;\n\t\t\t\treturn { ...node, data } as TNode;\n\t\t\t}\n\t\t\tconst next: Record<string, any> = { ...replacement };\n\t\t\tfor (const field of KEEP_FIELDS) {\n\t\t\t\tif (field in node) next[field] = node[field];\n\t\t\t\telse delete next[field];\n\t\t\t}\n\t\t\treturn next as TNode;\n\t\t});\n\n\t/** Runs a pure edit as one (optionally undoable) step; returns whether anything changed. */\n\tfunction commit(name: string, edit: (nodes: TNode[]) => TNode[], merge?: { tag: string; windowMs?: number; animationOnly?: boolean }): boolean {\n\t\tconst before = options.getNodes();\n\t\tconst after = edit(before);\n\t\tif (after === before) return false;\n\t\tconst beforeById = new Map(before.map((n) => [n.id, n]));\n\t\tconst undoMap = new Map<string, TNode>();\n\t\tconst redoMap = new Map<string, TNode>();\n\t\tfor (const node of after) {\n\t\t\tconst old = beforeById.get(node.id);\n\t\t\tif (old && old !== node) {\n\t\t\t\tundoMap.set(node.id, old);\n\t\t\t\tredoMap.set(node.id, node);\n\t\t\t}\n\t\t}\n\t\tif (redoMap.size === 0) return false;\n\t\tconst scope = merge?.animationOnly ?? false;\n\t\tconst execute = () => options.setNodes(replaceNodes(options.getNodes(), redoMap, scope));\n\t\tconst undo = () => options.setNodes(replaceNodes(options.getNodes(), undoMap, scope));\n\t\tconst command: Command = { name, execute, undo, ...(merge ? { tag: merge.tag } : {}) };\n\t\tif (!options.history) execute();\n\t\telse if (merge && options.history.executeMerged) options.history.executeMerged(command, { windowMs: merge.windowMs ?? 1000 });\n\t\telse options.history.execute(command);\n\t\treturn true;\n\t}\n\n\t/** Drops selected refs that no longer exist. */\n\tfunction pruneSelection() {\n\t\tif (selection.length === 0) return;\n\t\tconst byId = new Map(options.getNodes().map((n) => [n.id, n]));\n\t\tconst kept = selection.filter((ref) => {\n\t\t\tconst track = getNodeAnimation(byId.get(ref.nodeId))?.tracks[ref.property];\n\t\t\treturn !!track && normalizeKeyframes(track.keyframes).some((k) => Math.abs(k.time - ref.time) <= ANIMATION_TIME_EPSILON);\n\t\t});\n\t\tif (kept.length !== selection.length) selection = kept;\n\t}\n\n\tconst setSelection = (refs: KeyframeRef[]) => {\n\t\tselection = refs.map((r) => ({ nodeId: r.nodeId, property: r.property, time: r.time }));\n\t};\n\n\tlet displayCache: { nodes: readonly TNode[]; time: number; out: TNode[] } | null = null;\n\tfunction display(nodes: readonly TNode[] = options.getNodes()): TNode[] {\n\t\tconst time = player.currentTime;\n\t\tif (displayCache && displayCache.nodes === nodes && displayCache.time === time) return displayCache.out;\n\t\tconst out = applyAnimation(nodes, time, propertyOptions());\n\t\tdisplayCache = { nodes, time, out };\n\t\treturn out;\n\t}\n\n\tconst controller = {\n\t\tplayer,\n\t\tgetState,\n\t\tsubscribe(listener: (state: CanvasAnimationState) => void): () => void {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(listener);\n\t\t\t};\n\t\t},\n\t\t/** Re-reads node-derived state (the automatic duration) and notifies when it changed. */\n\t\trefresh() {\n\t\t\tconst prev = cached;\n\t\t\tif (getState() !== prev) notify();\n\t\t},\n\n\t\t// ----- playback ---------------------------------------------------------------------\n\t\tget time() {\n\t\t\treturn player.currentTime;\n\t\t},\n\t\tget playing() {\n\t\t\treturn player.isPlaying;\n\t\t},\n\t\tget duration() {\n\t\t\treturn duration();\n\t\t},\n\t\tplay: () => player.play(),\n\t\tpause: () => player.pause(),\n\t\ttoggle: () => player.toggle(),\n\t\tstop: () => player.stop(),\n\t\tseek: (time: number) => player.seek(time),\n\t\tscrub: (time: number) => player.scrub(time),\n\t\tendScrub: () => player.endScrub(),\n\t\tsetLoop: (loop: AnimationLoopMode) => player.setLoop(loop),\n\t\tsetPlaybackRate: (rate: number) => player.setPlaybackRate(rate),\n\t\t/** Fixed duration in ms, or `null` to follow the last keyframe. */\n\t\tsetDuration(ms: number | null) {\n\t\t\tfixedDuration = ms !== null && Number.isFinite(ms) && ms > 0 ? ms : null;\n\t\t\tdurationNodes = null;\n\t\t\tplayer.setDuration(duration);\n\t\t\tnotify();\n\t\t},\n\t\tsetFps(value: number) {\n\t\t\tif (!(value > 0) || value === fps) return;\n\t\t\tfps = value;\n\t\t\tnotify();\n\t\t},\n\t\tsetRecording(on: boolean) {\n\t\t\tif (recording === on) return;\n\t\t\trecording = on;\n\t\t\tnotify();\n\t\t},\n\t\ttoggleRecording() {\n\t\t\tcontroller.setRecording(!recording);\n\t\t},\n\t\t/** Seeks to the next keyframe (of the selected nodes, or all) after the playhead. */\n\t\tnextKeyframe(ids: Iterable<string> = selectedIds()): number | null {\n\t\t\tconst list = [...ids];\n\t\t\tconst times = getKeyframeTimes(options.getNodes(), list.length ? { ids: list } : {});\n\t\t\tconst next = times.find((t) => t > player.currentTime + ANIMATION_TIME_EPSILON);\n\t\t\tif (next === undefined) return null;\n\t\t\tplayer.seek(next);\n\t\t\treturn next;\n\t\t},\n\t\tpreviousKeyframe(ids: Iterable<string> = selectedIds()): number | null {\n\t\t\tconst list = [...ids];\n\t\t\tconst times = getKeyframeTimes(options.getNodes(), list.length ? { ids: list } : {});\n\t\t\tconst prev = [...times].reverse().find((t) => t < player.currentTime - ANIMATION_TIME_EPSILON);\n\t\t\tif (prev === undefined) return null;\n\t\t\tplayer.seek(prev);\n\t\t\treturn prev;\n\t\t},\n\n\t\t// ----- display ----------------------------------------------------------------------\n\t\t/** The nodes evaluated at the playhead (same array when nothing is animated). */\n\t\tdisplay,\n\t\t/**\n\t\t * Applies edits made on the displayed nodes: animated properties (and, in record mode,\n\t\t * any property) are keyed at the playhead; other changes go to the stored nodes.\n\t\t * Keying is one undo step per node set, merged while a drag continues.\n\t\t */\n\t\tapplyDisplayChanges(next: TNode[]): KeyframeRef[] {\n\t\t\tconst base = options.getNodes();\n\t\t\tconst shown = display(base);\n\t\t\tconst time = player.currentTime;\n\t\t\tconst { nodes, keyed } = recordAnimationChanges(base, shown, next, time, { recording, ...propertyOptions() });\n\t\t\tif (keyed.length === 0) {\n\t\t\t\tif (nodes !== base) options.setNodes(nodes);\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\t// Static edits apply directly; the keyframes are the undoable part.\n\t\t\tconst ids = new Set(keyed.map((k) => k.nodeId));\n\t\t\tconst baseById = new Map(base.map((n) => [n.id, n]));\n\t\t\tconst staticNodes = nodes.map((n) => {\n\t\t\t\tif (!ids.has(n.id)) return n;\n\t\t\t\tconst old = baseById.get(n.id)?.data?.[ANIMATION_DATA_KEY];\n\t\t\t\tconst data: Record<string, any> = { ...(n.data ?? {}) };\n\t\t\t\tif (old === undefined) delete data[ANIMATION_DATA_KEY];\n\t\t\t\telse data[ANIMATION_DATA_KEY] = old;\n\t\t\t\treturn { ...n, data } as TNode;\n\t\t\t});\n\t\t\tconst animationOf = new Map(nodes.filter((n) => ids.has(n.id)).map((n) => [n.id, n.data?.[ANIMATION_DATA_KEY]]));\n\t\t\toptions.setNodes(staticNodes);\n\t\t\tconst tag = `animation.record:${[...ids].sort().join(',')}`;\n\t\t\tcommit(\n\t\t\t\t'Record Keyframe',\n\t\t\t\t(current) =>\n\t\t\t\t\tcurrent.map((n) => {\n\t\t\t\t\t\tif (!animationOf.has(n.id)) return n;\n\t\t\t\t\t\tconst data: Record<string, any> = { ...(n.data ?? {}) };\n\t\t\t\t\t\tconst value = animationOf.get(n.id);\n\t\t\t\t\t\tif (value === undefined) delete data[ANIMATION_DATA_KEY];\n\t\t\t\t\t\telse data[ANIMATION_DATA_KEY] = value;\n\t\t\t\t\t\treturn { ...n, data } as TNode;\n\t\t\t\t\t}),\n\t\t\t\t{ tag, animationOnly: true }\n\t\t\t);\n\t\t\treturn keyed;\n\t\t},\n\n\t\t// ----- editing ----------------------------------------------------------------------\n\t\t/** Keys the nodes (default: selected) at the playhead; see `addKeyframesAtTime`. */\n\t\taddKeyframe(ids?: Iterable<string>, keys?: readonly string[], time = player.currentTime): KeyframeRef[] {\n\t\t\tlet refs: KeyframeRef[] = [];\n\t\t\tcommit('Add Keyframe', (nodes) => {\n\t\t\t\tconst result = addKeyframesAtTime(nodes, ids ?? selectedIds(), time, { ...propertyOptions(), ...(keys ? { keys } : {}) });\n\t\t\t\trefs = result.refs;\n\t\t\t\treturn result.nodes;\n\t\t\t});\n\t\t\tif (refs.length) setSelection(refs);\n\t\t\tnotify();\n\t\t\treturn refs;\n\t\t},\n\t\tsetKeyframe(nodeId: string, property: string, value: AnimationValue, time = player.currentTime, easing?: AnimationEasing | string) {\n\t\t\tcommit('Set Keyframe', (nodes) => setKeyframe(nodes, nodeId, property, time, value, { ...propertyOptions(), ...(easing !== undefined ? { easing } : {}) }));\n\t\t},\n\t\tupdateKeyframe(ref: KeyframeRef, patch: KeyframePatch) {\n\t\t\tcommit('Edit Keyframe', (nodes) => updateKeyframe(nodes, ref, patch, propertyOptions()));\n\t\t\tif (patch.time !== undefined && isKeyframeSelected(selection, ref)) {\n\t\t\t\tsetSelection(selection.map((s) => (isKeyframeSelected([ref], s) ? { ...s, time: Math.max(0, patch.time!) } : s)));\n\t\t\t}\n\t\t\tpruneSelection();\n\t\t\tnotify();\n\t\t},\n\t\tdeleteKeyframes(refs: readonly KeyframeRef[] = selection): boolean {\n\t\t\tif (refs.length === 0) return false;\n\t\t\tconst changed = commit(refs.length === 1 ? 'Delete Keyframe' : 'Delete Keyframes', (nodes) => deleteKeyframes(nodes, refs, propertyOptions()));\n\t\t\tpruneSelection();\n\t\t\tnotify();\n\t\t\treturn changed;\n\t\t},\n\t\tsetEasing(easing: AnimationEasing | string | null, refs: readonly KeyframeRef[] = selection): boolean {\n\t\t\tif (refs.length === 0) return false;\n\t\t\treturn commit('Set Easing', (nodes) => setKeyframeEasing(nodes, refs, easing, propertyOptions()));\n\t\t},\n\t\tsetTrackEnabled(nodeId: string, property: string, enabled: boolean) {\n\t\t\tcommit(enabled ? 'Enable Track' : 'Disable Track', (nodes) => setTrackEnabled(nodes, nodeId, property, enabled));\n\t\t},\n\t\tremoveTrack(nodeId: string, property: string) {\n\t\t\tcommit('Remove Track', (nodes) => removeTrack(nodes, nodeId, property, { ...propertyOptions(), keepValueAt: player.currentTime }));\n\t\t\tpruneSelection();\n\t\t\tnotify();\n\t\t},\n\t\tsetMotionPath(nodeId: string, motionPath: { d: string; autoRotate?: boolean } | null) {\n\t\t\tcommit('Set Motion Path', (nodes) => setMotionPath(nodes, nodeId, motionPath));\n\t\t},\n\t\t/** Animates a node to `to` (from its current values) over `duration`; see `animateNode`. */\n\t\tanimateNode(nodeId: string, opts: Omit<AnimateNodeOptions, 'properties'>): KeyframeRef[] {\n\t\t\tlet refs: KeyframeRef[] = [];\n\t\t\tcommit('Animate', (nodes) => {\n\t\t\t\tconst result = animateNode(nodes, nodeId, { ...opts, ...propertyOptions() });\n\t\t\t\trefs = result.refs;\n\t\t\t\treturn result.nodes;\n\t\t\t});\n\t\t\treturn refs;\n\t\t},\n\t\t/** Moves keyframes (default: selected) by `delta` ms as one step; returns the delta applied. */\n\t\tmoveKeyframes(delta: number, refs: readonly KeyframeRef[] = selection): number {\n\t\t\tif (refs.length === 0) return 0;\n\t\t\tlet applied = 0;\n\t\t\tlet moved: KeyframeRef[] = [];\n\t\t\tcommit(refs.length === 1 ? 'Move Keyframe' : 'Move Keyframes', (nodes) => {\n\t\t\t\tconst result = moveKeyframes(nodes, refs, delta);\n\t\t\t\tapplied = result.delta;\n\t\t\t\tmoved = result.refs;\n\t\t\t\treturn result.nodes;\n\t\t\t});\n\t\t\tif (applied !== 0 && refs === selection) setSelection(moved);\n\t\t\tnotify();\n\t\t\treturn applied;\n\t\t},\n\t\t/** Scales keyframe spacing (default: selected) around `pivot`; returns the factor applied. */\n\t\tscaleKeyframes(factor: number, refs: readonly KeyframeRef[] = selection, pivot?: number): number {\n\t\t\tif (refs.length === 0) return 1;\n\t\t\tlet applied = 1;\n\t\t\tlet scaled: KeyframeRef[] = [];\n\t\t\tcommit('Scale Keyframes', (nodes) => {\n\t\t\t\tconst result = scaleKeyframes(nodes, refs, factor, pivot === undefined ? {} : { pivot });\n\t\t\t\tapplied = result.factor;\n\t\t\t\tscaled = result.refs;\n\t\t\t\treturn result.nodes;\n\t\t\t});\n\t\t\tif (applied !== 1 && refs === selection) setSelection(scaled);\n\t\t\tnotify();\n\t\t\treturn applied;\n\t\t},\n\t\tcopyKeyframes(refs: readonly KeyframeRef[] = selection): AnimationKeyframeClipboard | null {\n\t\t\tif (refs.length === 0) return null;\n\t\t\tclipboard = copyKeyframes(options.getNodes(), refs);\n\t\t\treturn clipboard;\n\t\t},\n\t\t/** Pastes at the playhead onto the selected nodes (or the copied ones). */\n\t\tpasteKeyframes(data: AnimationKeyframeClipboard | null = clipboard, nodeIds: readonly string[] = selectedIds(), time = player.currentTime): KeyframeRef[] {\n\t\t\tif (!data) return [];\n\t\t\tlet refs: KeyframeRef[] = [];\n\t\t\tcommit('Paste Keyframes', (nodes) => {\n\t\t\t\tconst result = pasteKeyframes(nodes, data, time, { ...propertyOptions(), nodeIds });\n\t\t\t\trefs = result.refs;\n\t\t\t\treturn result.nodes;\n\t\t\t});\n\t\t\tif (refs.length) setSelection(refs);\n\t\t\tnotify();\n\t\t\treturn refs;\n\t\t},\n\t\tget clipboard() {\n\t\t\treturn clipboard;\n\t\t},\n\n\t\t// ----- selection & drag -------------------------------------------------------------\n\t\tget selection() {\n\t\t\treturn selection as readonly KeyframeRef[];\n\t\t},\n\t\tselectKeyframes(refs: readonly KeyframeRef[], mode: 'replace' | 'add' | 'toggle' = 'replace') {\n\t\t\tif (mode === 'replace') setSelection([...refs]);\n\t\t\telse if (mode === 'add') setSelection([...selection, ...refs.filter((r) => !isKeyframeSelected(selection, r))]);\n\t\t\telse {\n\t\t\t\tconst removed = selection.filter((s) => !isKeyframeSelected(refs, s));\n\t\t\t\tsetSelection([...removed, ...refs.filter((r) => !isKeyframeSelected(selection, r))]);\n\t\t\t}\n\t\t\tnotify();\n\t\t},\n\t\tclearSelection() {\n\t\t\tif (selection.length === 0) return;\n\t\t\tselection = [];\n\t\t\tnotify();\n\t\t},\n\t\t/** Starts dragging keyframes (default: the selection; refs not selected become the selection). */\n\t\tbeginKeyframeDrag(refs?: readonly KeyframeRef[]) {\n\t\t\tif (refs && !refs.every((r) => isKeyframeSelected(selection, r))) setSelection([...refs]);\n\t\t\tif (selection.length === 0) return;\n\t\t\tdrag = { refs: selection.map((r) => ({ ...r })), delta: 0, target: null };\n\t\t\tnotify();\n\t\t},\n\t\t/**\n\t\t * Previews the drag at `delta` ms. With `snap` (default on) the group snaps to other\n\t\t * keyframes, the playhead, 0 and the end within `thresholdMs`, else to whole frames.\n\t\t */\n\t\tupdateKeyframeDrag(delta: number, opts: { snap?: boolean; thresholdMs?: number } = {}): KeyframeDragState | null {\n\t\t\tif (!drag) return null;\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst times = drag.refs.map((r) => r.time);\n\t\t\tconst minTime = Math.min(...times);\n\t\t\tlet d = Math.max(-minTime, Number.isFinite(delta) ? delta : 0);\n\t\t\tlet target: number | null = null;\n\t\t\tif (opts.snap !== false) {\n\t\t\t\tconst targets = uniq([...getKeyframeTimes(nodes, { exclude: drag.refs }), player.currentTime, 0, duration()]);\n\t\t\t\tconst snapped = snapKeyframeDelta(times, d, targets, opts.thresholdMs ?? 1000 / fps / 2);\n\t\t\t\ttarget = snapped.target;\n\t\t\t\td = snapped.delta;\n\t\t\t\tif (target === null) d = snapTimeToFrame(minTime + d, fps) - minTime;\n\t\t\t}\n\t\t\td = Math.max(-minTime, d);\n\t\t\tif (d !== drag.delta || target !== drag.target) {\n\t\t\t\tdrag = { ...drag, delta: d, target };\n\t\t\t\tnotify();\n\t\t\t}\n\t\t\treturn drag;\n\t\t},\n\t\t/** Commits the drag as one undo step. */\n\t\tendKeyframeDrag(): number {\n\t\t\tif (!drag) return 0;\n\t\t\tconst { refs, delta } = drag;\n\t\t\tdrag = null;\n\t\t\tlet applied = 0;\n\t\t\tif (delta !== 0) {\n\t\t\t\tlet moved: KeyframeRef[] = [];\n\t\t\t\tcommit(refs.length === 1 ? 'Move Keyframe' : 'Move Keyframes', (nodes) => {\n\t\t\t\t\tconst result = moveKeyframes(nodes, refs, delta);\n\t\t\t\t\tapplied = result.delta;\n\t\t\t\t\tmoved = result.refs;\n\t\t\t\t\treturn result.nodes;\n\t\t\t\t});\n\t\t\t\tif (applied !== 0) setSelection(moved);\n\t\t\t}\n\t\t\tnotify();\n\t\t\treturn applied;\n\t\t},\n\t\t/** Cancels the drag preview (Escape); nothing is edited. */\n\t\tcancelKeyframeDrag() {\n\t\t\tif (!drag) return;\n\t\t\tdrag = null;\n\t\t\tnotify();\n\t\t},\n\n\t\t// ----- views & export ---------------------------------------------------------------\n\t\t/** Timeline rows for `ids` (default: selected nodes), including the drag preview. */\n\t\ttimelineRows(ids: Iterable<string> = selectedIds(), opts: Omit<TimelineRowsOptions, 'selection' | 'dragDelta'> = {}): TimelineRow[] {\n\t\t\treturn getTimelineRows(options.getNodes(), ids, { ...propertyOptions(), ...opts, selection, dragDelta: drag?.delta ?? 0 });\n\t\t},\n\t\tsampleFrames(opts: SampleAnimationOptions = {}) {\n\t\t\treturn sampleAnimationFrames(options.getNodes(), { fps, duration: duration(), ...propertyOptions(), ...opts });\n\t\t},\n\t\texportCss(nodeId: string, opts: CssExportOptions & { name?: string; selector?: string } = {}) {\n\t\t\tconst state = player.getState();\n\t\t\treturn exportCssAnimation(options.getNodes(), nodeId, { duration: duration(), loop: state.loop, playbackRate: state.playbackRate, ...opts });\n\t\t},\n\t\ttoWebAnimations(nodeId: string, opts: CssExportOptions = {}) {\n\t\t\tconst state = player.getState();\n\t\t\treturn toWebAnimations(options.getNodes(), nodeId, { duration: duration(), loop: state.loop, playbackRate: state.playbackRate, ...opts });\n\t\t},\n\t\tdestroy() {\n\t\t\tplayer.destroy();\n\t\t\tlisteners.clear();\n\t\t}\n\t};\n\treturn controller;\n}\n\nexport type CanvasAnimationController<TNode extends CanvasNode = CanvasNode> = ReturnType<typeof createCanvasAnimationController<TNode>>;\n","// Portions ported from figcraft/crates/engine/src/components.rs and figcraft/crates/engine/src/cmd/component.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport { applyNodeUpdates, getAbsolutePosition, getNodeSize, readStyleSize, type CanvasNodeUpdate } from './arrange.js';\nimport { applyConstraints, getAutoLayout, getChildLayout, getLayoutSizing, layoutUpMany, type AutoLayoutOptions } from './autoLayout.js';\nimport {\n\tgetCanvasTokenProperty,\n\tparseNodeStyleDeclarations,\n\treadNodeStyleProperty,\n\tresolveCanvasTokens,\n\twriteNodeStyleProperty,\n\ttype CanvasTokenOptions\n} from './tokenBindings.js';\nimport type { TokenState } from '../tokens/index.js';\nimport type { CanvasNode, XYPosition } from './types.js';\n\n/**\n * Figma-style components and instances on the canvas.\n *\n * - A **main component** is a node with `data.component` (a frame whose children, via\n * `parentId`, are its layers). A **component set** (`data.componentSet`) holds variants:\n * components with `data.component.variant` (`{ Size: 'Small', Style: 'Primary' }`).\n * - An **instance** is a node with `data.instance = { componentId, properties }`. Its layers are\n * real nodes copied from the main; every copy records the main layer it mirrors in\n * `data.componentSource` and the property groups changed locally in\n * `data.componentOverrides` (`data.label`, `style.backgroundColor`, `shape.fill`,\n * `layout.gap`, `token.fill`, `hidden`, …).\n * - When a main changes, its instances are rebuilt from it, keeping layer ids and re-applying\n * overrides (matched by source, then by layer name path, so switching variants keeps\n * compatible overrides). Component properties (boolean → visibility, text → a `data` field,\n * instance swap → a nested instance's main) are applied through `data.componentPropertyRefs`\n * on the main's layers.\n * - Instances never gain or lose layers: deleting a layer inside an instance hides it, and\n * nodes added inside one move out next to it. Geometry of layers inside an instance follows\n * the main (and constraints / auto layout); only the instance itself resizes.\n * - An instance whose main was deleted keeps its layers (\"missing main\") and can restore it.\n *\n * Everything lives in `data`, so components and instances survive JSON, copy / paste and\n * duplication; `syncCanvasComponents(before, after)` relinks what a paste brought in.\n */\n\n/** Data key of a main component's definition. */\nexport const COMPONENT_DATA_KEY = 'component';\n/** Data key of a component set's definition (its variants are its child components). */\nexport const COMPONENT_SET_DATA_KEY = 'componentSet';\n/** Data key of an instance's link to its main (`componentId`) and its property values. */\nexport const INSTANCE_DATA_KEY = 'instance';\n/** Data key of the main-component layer an instance layer mirrors. */\nexport const COMPONENT_SOURCE_DATA_KEY = 'componentSource';\n/** Data key of the override groups of an instance layer. */\nexport const COMPONENT_OVERRIDES_DATA_KEY = 'componentOverrides';\n/** Data key of a main layer's component property bindings (field → property name). */\nexport const COMPONENT_PROPERTY_REFS_DATA_KEY = 'componentPropertyRefs';\n\n/** Nesting limit for instances inside instances. */\nconst MAX_DEPTH = 16;\n\nexport type CanvasComponentPropertyType = 'boolean' | 'text' | 'instanceSwap';\n\nexport type CanvasComponentPropertyDef =\n\t| { type: 'boolean'; default: boolean; description?: string }\n\t| { type: 'text'; default: string; description?: string }\n\t| { type: 'instanceSwap'; default: string; preferred?: string[]; description?: string };\n\nexport type CanvasComponentPropertyValue = boolean | string;\n\nexport interface CanvasComponentData {\n\tdescription?: string;\n\t/** Component properties (on a lone component; variants share their set's). */\n\tproperties?: Record<string, CanvasComponentPropertyDef>;\n\t/** Variant property values of a component inside a set. */\n\tvariant?: Record<string, string>;\n}\n\nexport interface CanvasComponentSetData {\n\tdescription?: string;\n\t/** Component properties shared by every variant. */\n\tproperties?: Record<string, CanvasComponentPropertyDef>;\n}\n\nexport interface CanvasInstanceData {\n\tcomponentId: string;\n\tproperties?: Record<string, CanvasComponentPropertyValue>;\n}\n\n/**\n * A field a component property drives on a main layer: `visible` (boolean), `component` (instance\n * swap, on a nested instance) or a `data.<key>` field (text, e.g. `data.label`).\n */\nexport type CanvasComponentPropertyField = 'visible' | 'component' | `data.${string}`;\n\nexport type CanvasComponentErrorCode =\n\t| 'not-found'\n\t| 'invalid'\n\t| 'cycle'\n\t| 'inside-instance'\n\t| 'nested-instance'\n\t| 'missing-main'\n\t| 'no-overrides'\n\t| 'unknown-property'\n\t| 'type-mismatch';\n\nexport type CanvasComponentResult<TNode extends CanvasNode = CanvasNode> =\n\t| { ok: true; nodes: TNode[]; ids: string[] }\n\t| { ok: false; nodes: TNode[]; code: CanvasComponentErrorCode; reason: string };\n\nexport interface CanvasComponentSyncOptions {\n\t/** Ids for new layer copies (default `<instanceId>:<sourceId>`, made unique). */\n\tidGenerator?: (instanceId: string, sourceId: string) => string;\n\t/** Node types whose overrides all survive variant switches (default `['text']`). */\n\ttextTypes?: readonly string[];\n\t/** Re-run auto layout inside rebuilt instances (default `true`). */\n\tlayout?: boolean | AutoLayoutOptions;\n\t/**\n\t * Resolves token bindings after a sync when `state` is given (bindings inside instances\n\t * resolve in the instance's own modes). Bindings are always compared, never values.\n\t */\n\ttokens?: CanvasTokenOptions & { state?: TokenState };\n}\n\nexport interface CreateCanvasComponentOptions extends CanvasComponentSyncOptions {\n\t/** Id of the wrapping frame (when the selection is wrapped). */\n\tid?: string;\n\t/** Node type of the wrapping frame (default `group`). */\n\tframeType?: string;\n\t/** Node types that become a component themselves instead of being wrapped (default `['group', 'frame']`). */\n\tframeTypes?: readonly string[];\n\t/** Name (`data.label`) of a wrapping frame. */\n\tname?: string;\n}\n\nexport interface CreateCanvasInstanceOptions extends CanvasComponentSyncOptions {\n\tid?: string;\n\t/** Frame to place the instance in (must not be inside its own main or an instance). */\n\tparentId?: string;\n\t/** Position in the parent's space (default: right of the main, or 0,0 in a parent). */\n\tposition?: XYPosition;\n\t/** Property values (variant properties pick the variant). */\n\tproperties?: Record<string, CanvasComponentPropertyValue>;\n}\n\n// ---------------------------------------------------------------------------------------------\n// Small helpers\n\ntype Data = Record<string, any>;\ntype Value = CanvasComponentPropertyValue;\n\nconst isRecord = (v: unknown): v is Record<string, any> => typeof v === 'object' && v !== null && !Array.isArray(v);\nconst dataOf = (node: CanvasNode | undefined | null): Data => (isRecord(node?.data) ? (node!.data as Data) : {});\n\n/** Structural equality of JSON-like values (`undefined` members are ignored). */\nfunction same(a: unknown, b: unknown): boolean {\n\tif (a === b) return true;\n\tif (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) {\n\t\treturn typeof a === 'number' && typeof b === 'number' && Number.isNaN(a) && Number.isNaN(b);\n\t}\n\tif (Array.isArray(a) !== Array.isArray(b)) return false;\n\tif (Array.isArray(a)) {\n\t\tconst other = b as unknown[];\n\t\treturn a.length === other.length && a.every((x, i) => same(x, other[i]));\n\t}\n\tconst ra = a as Record<string, unknown>;\n\tconst rb = b as Record<string, unknown>;\n\tconst ka = Object.keys(ra).filter((k) => ra[k] !== undefined);\n\tconst kb = Object.keys(rb).filter((k) => rb[k] !== undefined);\n\treturn ka.length === kb.length && ka.every((k) => same(ra[k], rb[k]));\n}\n\nfunction stringRecord(value: unknown): Record<string, string> {\n\tconst out: Record<string, string> = {};\n\tif (isRecord(value)) for (const [k, v] of Object.entries(value)) if (typeof v === 'string') out[k] = v;\n\treturn out;\n}\n\n/** Whether `node` is a main component. */\nexport function isCanvasComponent(node: CanvasNode | undefined | null): boolean {\n\treturn isRecord(dataOf(node)[COMPONENT_DATA_KEY]);\n}\n\n/** Whether `node` is a component set. */\nexport function isCanvasComponentSet(node: CanvasNode | undefined | null): boolean {\n\treturn isRecord(dataOf(node)[COMPONENT_SET_DATA_KEY]);\n}\n\n/** Whether `node` is an instance (top-level or nested). */\nexport function isCanvasInstance(node: CanvasNode | undefined | null): boolean {\n\tconst inst = dataOf(node)[INSTANCE_DATA_KEY];\n\treturn isRecord(inst) && typeof inst.componentId === 'string';\n}\n\n/** The instance data of `node`, if it is an instance. */\nexport function getInstanceData(node: CanvasNode | undefined | null): CanvasInstanceData | undefined {\n\treturn isCanvasInstance(node) ? (dataOf(node)[INSTANCE_DATA_KEY] as CanvasInstanceData) : undefined;\n}\n\n/** The component definition of `node`, if it is a main component. */\nexport function getComponentData(node: CanvasNode | undefined | null): CanvasComponentData | undefined {\n\treturn isCanvasComponent(node) ? (dataOf(node)[COMPONENT_DATA_KEY] as CanvasComponentData) : undefined;\n}\n\n/** The main-component layer an instance layer mirrors. */\nexport function getComponentSourceId(node: CanvasNode | undefined | null): string | undefined {\n\tconst s = dataOf(node)[COMPONENT_SOURCE_DATA_KEY];\n\treturn typeof s === 'string' ? s : undefined;\n}\n\n/** The override groups of an instance layer. */\nexport function getNodeOverrides(node: CanvasNode | undefined | null): readonly string[] {\n\tconst o = dataOf(node)[COMPONENT_OVERRIDES_DATA_KEY];\n\treturn Array.isArray(o) ? o.filter((g): g is string => typeof g === 'string') : [];\n}\n\n/** Component property bindings of a main layer (field → property name). */\nexport function getComponentPropertyRefs(node: CanvasNode | undefined | null): Readonly<Record<string, string>> {\n\treturn stringRecord(dataOf(node)[COMPONENT_PROPERTY_REFS_DATA_KEY]);\n}\n\n/** A layer's display name: `data.name`, then `data.label`, then `data.text`, then its type. */\nexport function getLayerName(node: CanvasNode | undefined | null): string {\n\tconst d = dataOf(node);\n\tfor (const key of ['name', 'label', 'text']) if (typeof d[key] === 'string' && d[key]) return d[key];\n\treturn typeof node?.type === 'string' ? node.type : '';\n}\n\nfunction withData<T extends CanvasNode>(node: T, data: Data): T {\n\treturn { ...node, data } as T;\n}\n\nfunction setOverrides<T extends CanvasNode>(node: T, groups: Iterable<string>): T {\n\tconst list = [...new Set(groups)].sort();\n\tconst data = { ...dataOf(node) };\n\tif (list.length) data[COMPONENT_OVERRIDES_DATA_KEY] = list;\n\telse delete data[COMPONENT_OVERRIDES_DATA_KEY];\n\treturn same(getNodeOverrides(node), list) ? node : withData(node, data);\n}\n\n/** Removes the component link keys (source, overrides, property refs) from a node's data. */\nfunction unlinked<T extends CanvasNode>(node: T, keepRefs = false): T {\n\tconst d = dataOf(node);\n\tif (!(COMPONENT_SOURCE_DATA_KEY in d) && !(COMPONENT_OVERRIDES_DATA_KEY in d) && (keepRefs || !(COMPONENT_PROPERTY_REFS_DATA_KEY in d))) return node;\n\tconst data = { ...d };\n\tdelete data[COMPONENT_SOURCE_DATA_KEY];\n\tdelete data[COMPONENT_OVERRIDES_DATA_KEY];\n\tif (!keepRefs) delete data[COMPONENT_PROPERTY_REFS_DATA_KEY];\n\treturn withData(node, data);\n}\n\n// ---------------------------------------------------------------------------------------------\n// Override groups: property paths of a node\n\nconst LINK_KEYS = new Set([\n\tCOMPONENT_DATA_KEY,\n\tCOMPONENT_SET_DATA_KEY,\n\tINSTANCE_DATA_KEY,\n\tCOMPONENT_SOURCE_DATA_KEY,\n\tCOMPONENT_OVERRIDES_DATA_KEY,\n\tCOMPONENT_PROPERTY_REFS_DATA_KEY\n]);\nconst NESTED_KEYS: Record<string, string> = { shape: 'shape', layout: 'layout', tokenBindings: 'token', tokenModes: 'tokenMode' };\nconst NESTED_PREFIX: Record<string, string> = { shape: 'shape', layout: 'layout', token: 'tokenBindings', tokenMode: 'tokenModes' };\n/** Node fields that are structure, runtime state or handled by their own group. */\nconst SKIPPED_NODE_KEYS = new Set([\n\t'id',\n\t'parentId',\n\t'parentNode',\n\t'position',\n\t'positionAbsolute',\n\t'selected',\n\t'dragging',\n\t'resizing',\n\t'measured',\n\t'width',\n\t'height',\n\t'style',\n\t'data',\n\t'hidden',\n\t'internals',\n\t'computed',\n\t'handleBounds'\n]);\nconst RUNTIME_KEYS = ['selected', 'dragging', 'resizing'] as const;\nconst UNITLESS = new Set(['opacity', 'zIndex', 'fontWeight', 'lineHeight', 'flex', 'flexGrow', 'flexShrink', 'order', 'zoom']);\n\n/** Groups an instance root keeps as its own (placement, visibility, lock, modes); never overrides. */\nconst OWN_ROOT_GROUPS = new Set([\n\t'position',\n\t'hidden',\n\t'component',\n\t'props',\n\t'data.locked',\n\t'data.lockedPrevious',\n\t'data.childLayout',\n\t'node.draggable',\n\t'node.selectable',\n\t'node.deletable',\n\t'node.connectable',\n\t'node.zIndex',\n\t'node.extent',\n\t'node.expandParent'\n]);\nconst isOwnRootGroup = (g: string) => OWN_ROOT_GROUPS.has(g) || g.startsWith('tokenMode.');\n\nconst camel = (name: string) => (name.startsWith('--') ? name : name.trim().toLowerCase().replace(/-([a-z])/g, (_, c: string) => c.toUpperCase()));\n\nfunction styleKeys(style: CanvasNode['style']): string[] {\n\tif (!style) return [];\n\tif (typeof style === 'object') return Object.keys(style).filter((k) => style[k] !== undefined);\n\treturn parseNodeStyleDeclarations(style).map(([k]) => camel(k));\n}\n\nfunction sizeOf(node: CanvasNode) {\n\tconst s = readStyleSize(node.style);\n\treturn { width: node.width, height: node.height, styleWidth: s.width, styleHeight: s.height };\n}\n\nconst splitGroup = (group: string): [string, string] => {\n\tconst dot = group.indexOf('.');\n\treturn dot < 0 ? [group, ''] : [group.slice(0, dot), group.slice(dot + 1)];\n};\n\n/** Reads one override group (`hidden`, `size`, `position`, `style.<prop>`, `data.<key>`, …) of a node. */\nexport function readOverrideGroup(node: CanvasNode, group: string): unknown {\n\tconst data = dataOf(node);\n\tswitch (group) {\n\t\tcase 'hidden':\n\t\t\treturn node.hidden === true;\n\t\tcase 'position':\n\t\t\treturn node.position;\n\t\tcase 'size':\n\t\t\treturn sizeOf(node);\n\t\tcase 'component':\n\t\t\treturn getInstanceData(node)?.componentId;\n\t\tcase 'props':\n\t\t\treturn getInstanceData(node)?.properties ?? {};\n\t}\n\tconst [kind, key] = splitGroup(group);\n\tswitch (kind) {\n\t\tcase 'style':\n\t\t\treturn readNodeStyleProperty(node.style, key);\n\t\tcase 'data':\n\t\t\treturn data[key];\n\t\tcase 'node':\n\t\t\treturn (node as Data)[key];\n\t}\n\tconst holder = NESTED_PREFIX[kind];\n\tif (holder) return isRecord(data[holder]) ? data[holder][key] : undefined;\n\treturn undefined;\n}\n\nfunction withSize<T extends CanvasNode>(node: T, value: unknown): T {\n\tconst v = (isRecord(value) ? value : {}) as ReturnType<typeof sizeOf>;\n\tconst next = { ...node } as Data;\n\tif (v.width === undefined) delete next.width;\n\telse next.width = v.width;\n\tif (v.height === undefined) delete next.height;\n\telse next.height = v.height;\n\tconst current = readStyleSize(node.style);\n\tlet style = node.style;\n\tif (v.styleWidth !== current.width) style = writeNodeStyleProperty(style, 'width', v.styleWidth);\n\tif (v.styleHeight !== current.height) style = writeNodeStyleProperty(style, 'height', v.styleHeight);\n\tif (style !== node.style) next.style = style;\n\tconst w = v.width ?? v.styleWidth;\n\tconst h = v.height ?? v.styleHeight;\n\tif (node.measured && (w !== undefined || h !== undefined)) {\n\t\tnext.measured = { ...node.measured, ...(w !== undefined ? { width: w } : {}), ...(h !== undefined ? { height: h } : {}) };\n\t}\n\treturn next as T;\n}\n\n/** Returns `node` with one override group set to `value` (the same node when nothing changes). */\nexport function writeOverrideGroup<T extends CanvasNode>(node: T, group: string, value: unknown): T {\n\tif (same(readOverrideGroup(node, group), value)) return node;\n\tconst next = { ...node } as Data;\n\tconst data = { ...dataOf(node) };\n\tswitch (group) {\n\t\tcase 'hidden':\n\t\t\tif (value) next.hidden = true;\n\t\t\telse delete next.hidden;\n\t\t\treturn next as T;\n\t\tcase 'position':\n\t\t\tif (isRecord(value)) next.position = { x: value.x, y: value.y };\n\t\t\treturn next as T;\n\t\tcase 'size':\n\t\t\treturn withSize(node, value);\n\t\tcase 'component': {\n\t\t\tconst inst = getInstanceData(node);\n\t\t\tif (!inst || typeof value !== 'string') return node;\n\t\t\tdata[INSTANCE_DATA_KEY] = { ...inst, componentId: value };\n\t\t\treturn withData(node, data);\n\t\t}\n\t\tcase 'props': {\n\t\t\tconst inst = getInstanceData(node);\n\t\t\tif (!inst) return node;\n\t\t\tdata[INSTANCE_DATA_KEY] = { ...inst, properties: isRecord(value) ? { ...value } : {} };\n\t\t\treturn withData(node, data);\n\t\t}\n\t}\n\tconst [kind, key] = splitGroup(group);\n\tif (!key) return node;\n\tswitch (kind) {\n\t\tcase 'style': {\n\t\t\tconst unitless = typeof value === 'number' && UNITLESS.has(key);\n\t\t\tnext.style = writeNodeStyleProperty(node.style, key, value as string | number | undefined, unitless);\n\t\t\treturn next as T;\n\t\t}\n\t\tcase 'data':\n\t\t\tif (LINK_KEYS.has(key) || key in NESTED_KEYS) return node;\n\t\t\tif (value === undefined) delete data[key];\n\t\t\telse data[key] = value;\n\t\t\treturn withData(node, data);\n\t\tcase 'node':\n\t\t\tif (SKIPPED_NODE_KEYS.has(key)) return node;\n\t\t\tif (value === undefined) delete next[key];\n\t\t\telse next[key] = value;\n\t\t\treturn next as T;\n\t}\n\tconst holder = NESTED_PREFIX[kind];\n\tif (!holder) return node;\n\tconst record = isRecord(data[holder]) ? { ...data[holder] } : {};\n\tif (value === undefined) delete record[key];\n\telse record[key] = value;\n\tif (Object.keys(record).length === 0 && (holder === 'tokenBindings' || holder === 'tokenModes')) delete data[holder];\n\telse data[holder] = record;\n\treturn withData(node, data);\n}\n\nfunction listGroups(node: CanvasNode, out: Set<string>) {\n\tout.add('hidden');\n\tout.add('size');\n\tout.add('position');\n\tfor (const k of styleKeys(node.style)) if (k !== 'width' && k !== 'height') out.add(`style.${k}`);\n\tconst data = dataOf(node);\n\tfor (const [k, v] of Object.entries(data)) {\n\t\tif (LINK_KEYS.has(k)) continue;\n\t\tconst prefix = NESTED_KEYS[k];\n\t\tif (prefix) {\n\t\t\tif (isRecord(v)) for (const sub of Object.keys(v)) out.add(`${prefix}.${sub}`);\n\t\t\tcontinue;\n\t\t}\n\t\tout.add(`data.${k}`);\n\t}\n\tif (isCanvasInstance(node)) {\n\t\tout.add('component');\n\t\tout.add('props');\n\t}\n\tfor (const k of Object.keys(node)) if (!SKIPPED_NODE_KEYS.has(k)) out.add(`node.${k}`);\n}\n\n/** Override groups whose values differ between two nodes. */\nexport function diffOverrideGroups(a: CanvasNode, b: CanvasNode): string[] {\n\tconst groups = new Set<string>();\n\tlistGroups(a, groups);\n\tlistGroups(b, groups);\n\treturn [...groups].filter((g) => !same(readOverrideGroup(a, g), readOverrideGroup(b, g)));\n}\n\nfunction copyGroups<T extends CanvasNode>(from: CanvasNode, to: T, groups: Iterable<string>): T {\n\tlet next = to;\n\tfor (const g of groups) next = writeOverrideGroup(next, g, readOverrideGroup(from, g));\n\treturn next;\n}\n\n/** The group a component property field drives. */\nfunction fieldGroup(field: string): string {\n\treturn field === 'visible' ? 'hidden' : field;\n}\n\n// ---------------------------------------------------------------------------------------------\n// Tree\n\nclass Graph {\n\treadonly byId: Map<string, CanvasNode>;\n\tprivate readonly kids = new Map<string, string[]>();\n\n\tconstructor(readonly nodes: readonly CanvasNode[]) {\n\t\tthis.byId = new Map(nodes.map((n) => [n.id, n]));\n\t\tfor (const n of nodes) {\n\t\t\tif (!n.parentId || !this.byId.has(n.parentId)) continue;\n\t\t\tlet list = this.kids.get(n.parentId);\n\t\t\tif (!list) this.kids.set(n.parentId, (list = []));\n\t\t\tlist.push(n.id);\n\t\t}\n\t}\n\n\tget(id: string | undefined): CanvasNode | undefined {\n\t\treturn id === undefined ? undefined : this.byId.get(id);\n\t}\n\n\tchildren(id: string): readonly string[] {\n\t\treturn this.kids.get(id) ?? [];\n\t}\n\n\t/** `id` (optionally) and its ancestors, nearest first. */\n\tchain(id: string, self = true): CanvasNode[] {\n\t\tconst out: CanvasNode[] = [];\n\t\tconst seen = new Set<string>();\n\t\tlet at = this.get(id);\n\t\tif (at && !self) {\n\t\t\tseen.add(at.id);\n\t\t\tat = this.get(at.parentId);\n\t\t}\n\t\twhile (at && !seen.has(at.id)) {\n\t\t\tseen.add(at.id);\n\t\t\tout.push(at);\n\t\t\tat = this.get(at.parentId);\n\t\t}\n\t\treturn out;\n\t}\n\n\t/** Whether `a` is a strict ancestor of `b`. */\n\tisAncestor(a: string, b: string): boolean {\n\t\treturn this.chain(b, false).some((n) => n.id === a);\n\t}\n\n\t/** `id` and its descendants, pre-order. */\n\tsubtree(id: string): string[] {\n\t\tconst out: string[] = [];\n\t\tconst seen = new Set<string>();\n\t\tconst stack = [id];\n\t\twhile (stack.length) {\n\t\t\tconst at = stack.pop()!;\n\t\t\tif (seen.has(at) || !this.byId.has(at)) continue;\n\t\t\tseen.add(at);\n\t\t\tout.push(at);\n\t\t\tconst kids = this.children(at);\n\t\t\tfor (let i = kids.length - 1; i >= 0; i--) stack.push(kids[i]);\n\t\t}\n\t\treturn out;\n\t}\n}\n\nconst owningComponentIn = (g: Graph, id: string) => g.chain(id).find(isCanvasComponent)?.id;\nconst owningInstanceIn = (g: Graph, id: string) => g.chain(id).find(isCanvasInstance)?.id;\nconst enclosingInstanceIn = (g: Graph, id: string) => g.chain(id, false).find(isCanvasInstance)?.id;\nfunction rootInstanceIn(g: Graph, id: string): string | undefined {\n\tconst list = g.chain(id).filter(isCanvasInstance);\n\treturn list[list.length - 1]?.id;\n}\n\nfunction variantSetIn(g: Graph, componentId: string): CanvasNode | undefined {\n\tconst comp = g.get(componentId);\n\tif (!isCanvasComponent(comp)) return undefined;\n\tconst parent = g.get(comp!.parentId);\n\treturn isCanvasComponentSet(parent) ? parent : undefined;\n}\n\n/** The variant property values of a component: `data.component.variant`, else parsed from its name. */\nexport function getVariantValues(node: CanvasNode | undefined | null): Record<string, string> {\n\tconst explicit = getComponentData(node)?.variant;\n\tif (isRecord(explicit) && Object.keys(explicit).length) return stringRecord(explicit);\n\tconst name = getLayerName(node);\n\treturn name.includes('=') ? parseVariantName(name) : {};\n}\n\n/** Parses a variant name `Size=Large, State=Hover` into its pairs. */\nexport function parseVariantName(name: string): Record<string, string> {\n\tconst out: Record<string, string> = {};\n\tfor (const part of name.split(',')) {\n\t\tconst eq = part.indexOf('=');\n\t\tif (eq < 0) continue;\n\t\tconst k = part.slice(0, eq).trim();\n\t\tif (k) out[k] = part.slice(eq + 1).trim();\n\t}\n\treturn out;\n}\n\n/** Formats variant pairs as a name, in `order` (other properties after, sorted). */\nexport function formatVariantName(pairs: Readonly<Record<string, string>>, order: readonly string[] = []): string {\n\tconst keys = order.filter((k) => k in pairs);\n\tkeys.push(...Object.keys(pairs).filter((k) => !order.includes(k)).sort());\n\treturn keys.map((k) => `${k}=${pairs[k]}`).join(', ');\n}\n\nfunction variantsIn(g: Graph, setId: string): CanvasNode[] {\n\treturn g.children(setId).map((id) => g.get(id)!).filter(isCanvasComponent);\n}\n\nfunction variantOptionsIn(g: Graph, setId: string): { name: string; values: string[] }[] {\n\tconst out: { name: string; values: string[] }[] = [];\n\tfor (const v of variantsIn(g, setId)) {\n\t\tfor (const [k, value] of Object.entries(getVariantValues(v))) {\n\t\t\tlet entry = out.find((o) => o.name === k);\n\t\t\tif (!entry) out.push((entry = { name: k, values: [] }));\n\t\t\tif (!entry.values.includes(value)) entry.values.push(value);\n\t\t}\n\t}\n\treturn out;\n}\n\n/** A set's default variant: the one nearest its top-left corner. */\nfunction defaultVariantIn(g: Graph, setId: string): string | undefined {\n\tlet best: { key: [number, number, number]; id: string } | undefined;\n\tfor (const v of variantsIn(g, setId)) {\n\t\tconst { x, y } = v.position;\n\t\tconst key: [number, number, number] = [x + y, y, x];\n\t\tif (!best || key[0] < best.key[0] || (key[0] === best.key[0] && (key[1] < best.key[1] || (key[1] === best.key[1] && key[2] < best.key[2])))) {\n\t\t\tbest = { key, id: v.id };\n\t\t}\n\t}\n\treturn best?.id;\n}\n\nfunction findVariantIn(g: Graph, setId: string, wanted: Readonly<Record<string, string>>): string | undefined {\n\tconst entries = Object.entries(wanted);\n\tlet best: { score: number; id: string } | undefined;\n\tfor (const v of variantsIn(g, setId)) {\n\t\tconst values = getVariantValues(v);\n\t\tconst score = entries.filter(([k, val]) => values[k] === val).length;\n\t\tif (score === entries.length) return v.id;\n\t\tif (!best || score > best.score) best = { score, id: v.id };\n\t}\n\treturn best?.id;\n}\n\n/** The main component a component or set id stands for (a set's default variant). */\nfunction mainOfIn(g: Graph, id: string): string | undefined {\n\tconst node = g.get(id);\n\tif (isCanvasComponent(node)) return id;\n\tif (isCanvasComponentSet(node)) return defaultVariantIn(g, id);\n\treturn undefined;\n}\n\nconst siblingVariants = (g: Graph, a: string | undefined, b: string | undefined) =>\n\t!!a && !!b && a !== b && !!variantSetIn(g, a) && variantSetIn(g, a) === variantSetIn(g, b);\n\nfunction propertyDefsIn(g: Graph, componentId: string): Record<string, CanvasComponentPropertyDef> {\n\tconst comp = g.get(componentId);\n\tif (!isCanvasComponent(comp)) return {};\n\tconst set = variantSetIn(g, componentId);\n\tconst owner = set ? dataOf(set)[COMPONENT_SET_DATA_KEY] : dataOf(comp)[COMPONENT_DATA_KEY];\n\treturn isRecord(owner?.properties) ? (owner.properties as Record<string, CanvasComponentPropertyDef>) : {};\n}\n\nfunction validValue(def: CanvasComponentPropertyDef, v: unknown): boolean {\n\treturn def.type === 'boolean' ? typeof v === 'boolean' : typeof v === 'string';\n}\n\nfunction effectivePropsIn(g: Graph, componentId: string, values: Readonly<Record<string, unknown>> | undefined): Record<string, Value> {\n\tconst out: Record<string, Value> = {};\n\tfor (const [k, def] of Object.entries(propertyDefsIn(g, componentId))) {\n\t\tconst v = values?.[k];\n\t\tout[k] = v !== undefined && validValue(def, v) ? (v as Value) : def.default;\n\t}\n\treturn out;\n}\n\n/** Components instantiated (transitively) inside component `componentId`. */\nfunction componentDepsIn(g: Graph, componentId: string): Set<string> {\n\tconst deps = new Set<string>();\n\tconst queue = [componentId];\n\twhile (queue.length) {\n\t\tconst c = queue.pop()!;\n\t\tfor (const id of g.subtree(c)) {\n\t\t\tconst target = getInstanceData(g.get(id))?.componentId;\n\t\t\tif (target && !deps.has(target)) {\n\t\t\t\tdeps.add(target);\n\t\t\t\tqueue.push(target);\n\t\t\t}\n\t\t}\n\t}\n\treturn deps;\n}\n\n/** Whether placing an instance of `componentId` inside `parentId` would nest a component in itself. */\nfunction createsCycleIn(g: Graph, componentId: string, parentId: string | undefined): boolean {\n\tif (!parentId) return false;\n\tconst hosts = g.chain(parentId).filter(isCanvasComponent).map((n) => n.id);\n\tif (hosts.length === 0) return false;\n\tconst deps = componentDepsIn(g, componentId);\n\tdeps.add(componentId);\n\treturn hosts.some((h) => deps.has(h));\n}\n\n/** Whether an instance of `componentId` placed in `parentId` would contain itself. */\nexport function wouldCreateComponentCycle(nodes: readonly CanvasNode[], componentId: string, parentId: string | undefined): boolean {\n\tconst g = new Graph(nodes);\n\treturn createsCycleIn(g, mainOfIn(g, componentId) ?? componentId, parentId);\n}\n\n// ---------------------------------------------------------------------------------------------\n// Public queries\n\n/** The component a node belongs to (itself, or its nearest component ancestor). */\nexport function getOwningComponent(nodes: readonly CanvasNode[], id: string): string | undefined {\n\treturn owningComponentIn(new Graph(nodes), id);\n}\n\n/** The innermost instance containing `id` (`id` itself when it is an instance). */\nexport function getOwningInstance(nodes: readonly CanvasNode[], id: string): string | undefined {\n\treturn owningInstanceIn(new Graph(nodes), id);\n}\n\n/** The outermost instance containing `id`. */\nexport function getRootInstance(nodes: readonly CanvasNode[], id: string): string | undefined {\n\treturn rootInstanceIn(new Graph(nodes), id);\n}\n\n/** Whether `id` is a layer inside an instance (not a top-level instance itself). */\nexport function isInsideInstance(nodes: readonly CanvasNode[], id: string): boolean {\n\treturn enclosingInstanceIn(new Graph(nodes), id) !== undefined;\n}\n\n/** Top-level and nested instances of `componentId`. */\nexport function getComponentInstances(nodes: readonly CanvasNode[], componentId: string): string[] {\n\treturn nodes.filter((n) => getInstanceData(n)?.componentId === componentId).map((n) => n.id);\n}\n\n/** `linked`, or `missing-main` when the instance's main component is gone. */\nexport function getInstanceStatus(nodes: readonly CanvasNode[], id: string): 'linked' | 'missing-main' | undefined {\n\tconst g = new Graph(nodes);\n\tconst inst = getInstanceData(g.get(id));\n\tif (!inst) return undefined;\n\treturn isCanvasComponent(g.get(inst.componentId)) ? 'linked' : 'missing-main';\n}\n\n/** The variant set of a component (its parent set), if any. */\nexport function getVariantSet(nodes: readonly CanvasNode[], componentId: string): string | undefined {\n\treturn variantSetIn(new Graph(nodes), componentId)?.id;\n}\n\n/** Variant properties of a set with their values, in first-seen order. */\nexport function getVariantOptions(nodes: readonly CanvasNode[], setId: string): { name: string; values: string[] }[] {\n\treturn variantOptionsIn(new Graph(nodes), setId);\n}\n\n/** The variant of `setId` matching `wanted` (all values, or else the most). */\nexport function findVariant(nodes: readonly CanvasNode[], setId: string, wanted: Readonly<Record<string, string>>): string | undefined {\n\treturn findVariantIn(new Graph(nodes), setId, wanted);\n}\n\n/** A set's default variant: the one in its top-left corner. */\nexport function getDefaultVariant(nodes: readonly CanvasNode[], setId: string): string | undefined {\n\treturn defaultVariantIn(new Graph(nodes), setId);\n}\n\n/** Variant combinations shared by two or more variants of a set (conflicts to resolve). */\nexport function getVariantConflicts(nodes: readonly CanvasNode[], setId: string): string[][] {\n\tconst by = new Map<string, string[]>();\n\tfor (const v of variantsIn(new Graph(nodes), setId)) {\n\t\tconst key = formatVariantName(getVariantValues(v));\n\t\tby.set(key, [...(by.get(key) ?? []), v.id]);\n\t}\n\treturn [...by.values()].filter((ids) => ids.length > 1);\n}\n\n/** Component property definitions of a component (its set's for variants). */\nexport function getComponentPropertyDefs(nodes: readonly CanvasNode[], componentId: string): Record<string, CanvasComponentPropertyDef> {\n\treturn { ...propertyDefsIn(new Graph(nodes), componentId) };\n}\n\n/** An instance's property values, with defaults for those it doesn't set. */\nexport function getInstanceProperties(nodes: readonly CanvasNode[], id: string): Record<string, Value> {\n\tconst g = new Graph(nodes);\n\tconst inst = getInstanceData(g.get(id));\n\treturn inst ? effectivePropsIn(g, inst.componentId, inst.properties) : {};\n}\n\nexport interface CanvasComponentInfo {\n\tid: string;\n\tname: string;\n\tsetId?: string;\n\tvariant?: Record<string, string>;\n\t/** Instances linked to it (top-level and nested). */\n\tinstanceCount: number;\n}\n\nexport interface CanvasComponentSetInfo {\n\tid: string;\n\tname: string;\n\tvariants: CanvasComponentInfo[];\n\toptions: { name: string; values: string[] }[];\n\tdefaultVariantId?: string;\n}\n\n/** Components (lone ones and variants) and component sets, in document order. */\nexport function listCanvasComponents(nodes: readonly CanvasNode[]): { components: CanvasComponentInfo[]; sets: CanvasComponentSetInfo[] } {\n\tconst g = new Graph(nodes);\n\tconst counts = new Map<string, number>();\n\tfor (const n of nodes) {\n\t\tconst c = getInstanceData(n)?.componentId;\n\t\tif (c) counts.set(c, (counts.get(c) ?? 0) + 1);\n\t}\n\tconst components: CanvasComponentInfo[] = [];\n\tfor (const n of nodes) {\n\t\tif (!isCanvasComponent(n)) continue;\n\t\tconst set = variantSetIn(g, n.id);\n\t\tcomponents.push({\n\t\t\tid: n.id,\n\t\t\tname: getLayerName(n) || n.id,\n\t\t\t...(set ? { setId: set.id, variant: getVariantValues(n) } : {}),\n\t\t\tinstanceCount: counts.get(n.id) ?? 0\n\t\t});\n\t}\n\tconst sets = nodes.filter(isCanvasComponentSet).map((s) => ({\n\t\tid: s.id,\n\t\tname: getLayerName(s) || s.id,\n\t\tvariants: components.filter((c) => c.setId === s.id),\n\t\toptions: variantOptionsIn(g, s.id),\n\t\tdefaultVariantId: defaultVariantIn(g, s.id)\n\t}));\n\treturn { components, sets };\n}\n\nexport interface CanvasInstancePropertyInfo {\n\tname: string;\n\ttype: CanvasComponentPropertyType;\n\tvalue: Value;\n\tdefault: Value;\n\t/** Instance swap: preferred components (all components when empty). */\n\tpreferred?: string[];\n}\n\nexport interface CanvasInstanceInfo {\n\t/** The instance (the node itself, or the instance owning the inspected layer). */\n\tid: string;\n\t/** The outermost instance (where reset / push / detach act). */\n\trootId: string;\n\tnested: boolean;\n\tcomponentId: string;\n\tcomponentName?: string;\n\tstatus: 'linked' | 'missing-main';\n\tsetId?: string;\n\tvariant: Record<string, string>;\n\tvariantOptions: { name: string; values: string[] }[];\n\tproperties: CanvasInstancePropertyInfo[];\n\t/** Layers of the instance with overrides (the instance itself included). */\n\toverrides: { id: string; name: string; groups: string[] }[];\n}\n\n/** Everything an instance inspector shows, for an instance or a layer inside one. */\nexport function inspectCanvasInstance(nodes: readonly CanvasNode[], id: string): CanvasInstanceInfo | undefined {\n\tconst g = new Graph(nodes);\n\tconst instId = owningInstanceIn(g, id);\n\tif (!instId) return undefined;\n\tconst inst = g.get(instId)!;\n\tconst data = getInstanceData(inst)!;\n\tconst comp = g.get(data.componentId);\n\tconst linked = isCanvasComponent(comp);\n\tconst set = linked ? variantSetIn(g, data.componentId) : undefined;\n\tconst defs = propertyDefsIn(g, data.componentId);\n\tconst values = effectivePropsIn(g, data.componentId, data.properties);\n\tconst overrides: CanvasInstanceInfo['overrides'] = [];\n\tfor (const d of g.subtree(instId)) {\n\t\tconst groups = getNodeOverrides(g.get(d));\n\t\tif (groups.length) overrides.push({ id: d, name: getLayerName(g.get(d)) || d, groups: [...groups] });\n\t}\n\treturn {\n\t\tid: instId,\n\t\trootId: rootInstanceIn(g, instId) ?? instId,\n\t\tnested: enclosingInstanceIn(g, instId) !== undefined,\n\t\tcomponentId: data.componentId,\n\t\t...(linked ? { componentName: getLayerName(comp) || comp!.id } : {}),\n\t\tstatus: linked ? 'linked' : 'missing-main',\n\t\t...(set ? { setId: set.id } : {}),\n\t\tvariant: linked && set ? getVariantValues(comp) : {},\n\t\tvariantOptions: set ? variantOptionsIn(g, set.id) : [],\n\t\tproperties: Object.entries(defs).map(([name, def]) => ({\n\t\t\tname,\n\t\t\ttype: def.type,\n\t\t\tvalue: values[name],\n\t\t\tdefault: def.default,\n\t\t\t...(def.type === 'instanceSwap' ? { preferred: [...(def.preferred ?? [])] } : {})\n\t\t})),\n\t\toverrides\n\t};\n}\n\n// ---------------------------------------------------------------------------------------------\n// Instance rebuild\n\ninterface ResolvedSync {\n\tidGenerator: (instanceId: string, sourceId: string) => string;\n\ttextTypes: readonly string[];\n\tlayout: false | AutoLayoutOptions;\n\ttokens?: CanvasTokenOptions & { state?: TokenState };\n}\n\nfunction resolveSync(options: CanvasComponentSyncOptions = {}): ResolvedSync {\n\treturn {\n\t\tidGenerator: options.idGenerator ?? ((inst, src) => `${inst}:${src}`),\n\t\ttextTypes: options.textTypes ?? ['text'],\n\t\tlayout: options.layout === false ? false : options.layout === true || options.layout === undefined ? {} : options.layout,\n\t\ttokens: options.tokens\n\t};\n}\n\nconst stripLook = (data: Data): Data => {\n\tconst out = { ...data };\n\tfor (const k of LINK_KEYS) delete out[k];\n\treturn out;\n};\n\nfunction clearRuntime(node: Data, old: CanvasNode | undefined) {\n\tfor (const k of RUNTIME_KEYS) {\n\t\tif (old && (old as Data)[k] !== undefined) node[k] = (old as Data)[k];\n\t\telse delete node[k];\n\t}\n}\n\n/**\n * The value groups a node's token bindings write: values from variables are never overrides\n * (a different binding is, as `token.<key>`).\n */\nfunction tokenDrivenGroups(n: CanvasNode, options: CanvasTokenOptions | undefined): string[] {\n\tconst mine = stringRecord(dataOf(n).tokenBindings);\n\tconst out: string[] = [];\n\tfor (const key of Object.keys(mine)) {\n\t\tconst property = getCanvasTokenProperty(key, options);\n\t\tif (!property || !property.appliesTo(n)) continue;\n\t\tconst current = property.read(n);\n\t\tconst probe =\n\t\t\ttypeof current === 'number' ? current + 1 : typeof current === 'boolean' ? !current : `${current ?? ''}\\u0001`;\n\t\tfor (const g of diffOverrideGroups(n, property.write(n, probe as never))) if (!out.includes(g)) out.push(g);\n\t}\n\treturn out;\n}\n\n/** Groups a main layer's component property bindings set (values from instances, not overrides). */\nfunction propertyDrivenGroups(g: Graph, src: CanvasNode): string[] {\n\tconst out: string[] = [];\n\tlet at: CanvasNode | undefined = src;\n\tfor (let i = 0; at && i <= MAX_DEPTH; i++) {\n\t\tfor (const field of Object.keys(getComponentPropertyRefs(at))) out.push(fieldGroup(field));\n\t\tat = g.get(getComponentSourceId(at));\n\t}\n\treturn out;\n}\n\n/** What an instance root is compared with: its main, named after its set for variants. */\nfunction rootReference(g: Graph, comp: CanvasNode): CanvasNode {\n\tconst set = variantSetIn(g, comp.id);\n\tif (!set) return comp;\n\tconst label = dataOf(set).label;\n\treturn 'label' in dataOf(comp) && typeof label === 'string' ? withData(comp, { ...dataOf(comp), label }) : comp;\n}\n\n/** `measured` follows an explicit size (xyflow re-measures; geometry helpers read it first). */\nfunction fixMeasured(node: CanvasNode, old: CanvasNode | undefined): CanvasNode {\n\tconst measured = old?.measured ?? node.measured;\n\tconst size = sizeOf(node);\n\tconst w = size.width ?? size.styleWidth;\n\tconst h = size.height ?? size.styleHeight;\n\tif (!measured) {\n\t\tif (node.measured === undefined) return node;\n\t\tconst next = { ...node };\n\t\tdelete next.measured;\n\t\treturn next;\n\t}\n\tconst fixed = { ...measured, ...(w !== undefined ? { width: w } : {}), ...(h !== undefined ? { height: h } : {}) };\n\treturn same(fixed, node.measured) ? node : { ...node, measured: fixed };\n}\n\nfunction applyProp(g: Graph, node: CanvasNode, field: string, value: Value): CanvasNode {\n\tif (field === 'visible') return typeof value === 'boolean' ? writeOverrideGroup(node, 'hidden', !value) : node;\n\tif (field === 'component') {\n\t\tif (typeof value !== 'string' || !isCanvasInstance(node)) return node;\n\t\tconst main = mainOfIn(g, value);\n\t\treturn main ? writeOverrideGroup(node, 'component', main) : node;\n\t}\n\tif (field.startsWith('data.')) return writeOverrideGroup(node, field, typeof value === 'string' ? value : String(value));\n\treturn node;\n}\n\ninterface BuiltInstance {\n\t/** The rebuilt instance: its root, its layers (pre-order) and kept foreign content. */\n\tblock: CanvasNode[];\n\t/** Ids the block replaces. */\n\tremoved: Set<string>;\n}\n\n/**\n * Rebuilds the outermost instance `instId` from its main component (read from `g`), keeping\n * layer ids, overrides, property values and its own placement. `taken` holds every id in use\n * (new ids are added). Returns `undefined` when the main is missing (the instance stays as it is).\n */\nfunction buildInstance(g: Graph, instId: string, opts: ResolvedSync, taken: Set<string>): BuiltInstance | undefined {\n\tconst inst = g.get(instId);\n\tconst instData = getInstanceData(inst);\n\tif (!inst || !instData) return undefined;\n\tconst compId = instData.componentId;\n\tconst comp = g.get(compId);\n\tif (!comp || !isCanvasComponent(comp) || compId === instId || g.isAncestor(instId, compId) || g.isAncestor(compId, instId)) return undefined;\n\n\t// Old layers (kept for their ids and overrides) and foreign content (kept where it was).\n\tconst oldIds: string[] = [];\n\tconst foreign: string[] = [];\n\tconst stack = [...g.children(instId)];\n\twhile (stack.length) {\n\t\tconst id = stack.pop()!;\n\t\tif (getComponentSourceId(g.get(id)) !== undefined) {\n\t\t\toldIds.push(id);\n\t\t\tstack.push(...g.children(id));\n\t\t} else foreign.push(...g.subtree(id));\n\t}\n\tconst old = new Map(oldIds.map((id) => [id, g.get(id)!]));\n\tconst byParentSource = new Map<string, string>();\n\tconst bySource = new Map<string, string | null>();\n\tfor (const id of oldIds) {\n\t\tconst n = old.get(id)!;\n\t\tconst s = getComponentSourceId(n)!;\n\t\tconst key = `${n.parentId}\\u0000${s}`;\n\t\tif (!byParentSource.has(key)) byParentSource.set(key, id);\n\t\tbySource.set(s, bySource.has(s) ? null : id);\n\t}\n\tlet byPath: Map<string, string> | undefined;\n\tconst oldPaths = () => {\n\t\tconst out = new Map<string, string>();\n\t\tconst walk = (parent: string, prefix: string) => {\n\t\t\tconst counts = new Map<string, number>();\n\t\t\tfor (const c of g.children(parent)) {\n\t\t\t\tconst n = g.get(c)!;\n\t\t\t\tconst src = getComponentSourceId(n);\n\t\t\t\tif (src === undefined) continue;\n\t\t\t\tconst name = getLayerName(g.get(src) ?? n);\n\t\t\t\tconst k = counts.get(name) ?? 0;\n\t\t\t\tcounts.set(name, k + 1);\n\t\t\t\tconst path = `${prefix}/${k === 0 ? name : `${name}#${k}`}`;\n\t\t\t\tif (!out.has(path)) out.set(path, c);\n\t\t\t\twalk(c, path);\n\t\t\t}\n\t\t};\n\t\twalk(instId, '');\n\t\treturn out;\n\t};\n\n\tconst used = new Set<string>();\n\tconst fresh = (sourceId: string) => {\n\t\tconst base = opts.idGenerator(instId, sourceId);\n\t\tlet id = base;\n\t\tfor (let k = 2; taken.has(id); k++) id = `${base}~${k}`;\n\t\ttaken.add(id);\n\t\treturn id;\n\t};\n\tconst instProps = new Map<string, Record<string, Value>>();\n\tconst out: CanvasNode[] = [];\n\tconst isText = (n: CanvasNode) => typeof n.type === 'string' && opts.textTypes.includes(n.type);\n\n\t/** Copy of main layer `s` (with `o`'s overrides when matched). */\n\tconst copyLayer = (s: CanvasNode, o: CanvasNode | undefined, id: string, parentId: string): CanvasNode => {\n\t\tconst next = { ...s, id, parentId } as Data;\n\t\tconst data = stripLook(dataOf(s));\n\t\tif (isCanvasInstance(s)) data[INSTANCE_DATA_KEY] = { ...dataOf(s)[INSTANCE_DATA_KEY] };\n\t\tdata[COMPONENT_SOURCE_DATA_KEY] = s.id;\n\t\tnext.data = data;\n\t\tclearRuntime(next, o);\n\t\tlet n = next as CanvasNode;\n\t\tif (o) {\n\t\t\tlet groups = [...getNodeOverrides(o)];\n\t\t\tconst from = g.get(getComponentSourceId(o));\n\t\t\tif (from && from.id !== s.id && groups.length && !isText(o) && siblingVariants(g, owningComponentIn(g, from.id), owningComponentIn(g, s.id))) {\n\t\t\t\t// Switched variants: keep overrides of properties both variants' layers agreed on.\n\t\t\t\tconst differs = new Set(diffOverrideGroups(from, s));\n\t\t\t\tgroups = groups.filter((gr) => !differs.has(gr));\n\t\t\t}\n\t\t\tn = copyGroups(o, n, groups);\n\t\t\tn = keepLaidOut(s, o, n, groups);\n\t\t\tn = setOverrides(n, groups);\n\t\t}\n\t\treturn fixMeasured(n, o);\n\t};\n\n\t/** Where auto layout places / sizes a copy, keep the old copy's values (layout re-flows). */\n\tconst keepLaidOut = (s: CanvasNode, o: CanvasNode, n: CanvasNode, groups: readonly string[]): CanvasNode => {\n\t\tif (o.parentId !== n.parentId || groups.includes('size') || groups.includes('position')) return n;\n\t\tconst flows = !getChildLayout(n).absolute && !!getAutoLayout(g.get(s.parentId));\n\t\tlet next = n;\n\t\tif (flows) next = writeOverrideGroup(next, 'position', o.position);\n\t\tconst derived = (axis: 'width' | 'height') => {\n\t\t\tconst sizing = getLayoutSizing(next, axis);\n\t\t\treturn (sizing === 'hug' && !!getAutoLayout(next)) || (sizing === 'fill' && flows);\n\t\t};\n\t\tconst size = { ...sizeOf(next) };\n\t\tconst oldSize = sizeOf(o);\n\t\tif (derived('width')) {\n\t\t\tsize.width = oldSize.width;\n\t\t\tsize.styleWidth = oldSize.styleWidth;\n\t\t}\n\t\tif (derived('height')) {\n\t\t\tsize.height = oldSize.height;\n\t\t\tsize.styleHeight = oldSize.styleHeight;\n\t\t}\n\t\treturn writeOverrideGroup(next, 'size', size);\n\t};\n\n\t/**\n\t * An instance root looking like `look`, placed like `place`, with `old`'s overrides. A\n\t * top-level instance keeps its own placement, visibility, lock and modes; a swapped nested\n\t * instance is placed like the main layer it stands in for.\n\t */\n\tconst instanceRoot = (\n\t\tlook: CanvasNode,\n\t\tplace: CanvasNode,\n\t\told: CanvasNode | undefined,\n\t\tid: string,\n\t\tparentId: string | undefined,\n\t\tsourceId: string,\n\t\tinstance: CanvasInstanceData,\n\t\tpreviousComponent: string | undefined,\n\t\ttopLevel: boolean\n\t): CanvasNode => {\n\t\tconst reference = rootReference(g, look);\n\t\tconst next = { ...reference, id, position: { ...place.position } } as Data;\n\t\tif (parentId === undefined) delete next.parentId;\n\t\telse next.parentId = parentId;\n\t\tconst data = stripLook(dataOf(reference));\n\t\tdelete data.tokenModes;\n\t\tdata[INSTANCE_DATA_KEY] = { ...instance };\n\t\tdata[COMPONENT_SOURCE_DATA_KEY] = sourceId;\n\t\tnext.data = data;\n\t\tclearRuntime(next, old);\n\t\tlet n = next as CanvasNode;\n\t\tconst own = new Set<string>();\n\t\tlistGroups(place, own);\n\t\tlistGroups(n, own);\n\t\tn = copyGroups(place, n, [...own].filter((gr) => isOwnRootGroup(gr) && gr !== 'component' && gr !== 'props'));\n\t\tlet groups = old ? getNodeOverrides(old).filter((gr) => !topLevel || !isOwnRootGroup(gr)) : [];\n\t\tif (old && previousComponent && siblingVariants(g, previousComponent, look.id)) {\n\t\t\t// Switched variants: keep overrides where both variants agreed.\n\t\t\tconst differs = new Set(diffOverrideGroups(g.get(previousComponent)!, look));\n\t\t\tgroups = groups.filter((gr) => gr === 'data.label' || gr === 'data.name' || !differs.has(gr));\n\t\t}\n\t\t// The instance data (main and property values) was resolved by the caller.\n\t\tif (old) n = copyGroups(old, n, groups.filter((gr) => gr !== 'component' && gr !== 'props'));\n\t\treturn fixMeasured(setOverrides(n, groups), old);\n\t};\n\n\tconst applyChainProps = (n: CanvasNode, s: CanvasNode, enc: readonly string[]): CanvasNode => {\n\t\tconst chain: CanvasNode[] = [s];\n\t\twhile (chain.length < enc.length) {\n\t\t\tconst next = g.get(getComponentSourceId(chain[chain.length - 1]));\n\t\t\tif (!next) break;\n\t\t\tchain.push(next);\n\t\t}\n\t\tlet next = n;\n\t\tfor (let j = chain.length - 1; j >= 0; j--) {\n\t\t\tconst props = instProps.get(enc[j]);\n\t\t\tif (!props) continue;\n\t\t\tfor (const [field, prop] of Object.entries(getComponentPropertyRefs(chain[j]))) {\n\t\t\t\tconst group = fieldGroup(field);\n\t\t\t\tif (getNodeOverrides(next).includes(group) || (j > 0 && getNodeOverrides(s).includes(group))) continue;\n\t\t\t\tif (prop in props) next = applyProp(g, next, field, props[prop]);\n\t\t\t}\n\t\t}\n\t\treturn next;\n\t};\n\n\tconst copyChildren = (srcParent: string, newParent: string, prefix: string, enc: string[], expanding: string[], depth: number) => {\n\t\tconst counts = new Map<string, number>();\n\t\tfor (const sid of g.children(srcParent)) {\n\t\t\tconst s = g.get(sid)!;\n\t\t\tconst name = getLayerName(s);\n\t\t\tconst k = counts.get(name) ?? 0;\n\t\t\tcounts.set(name, k + 1);\n\t\t\tconst path = `${prefix}/${k === 0 ? name : `${name}#${k}`}`;\n\t\t\tlet oid = byParentSource.get(`${newParent}\\u0000${sid}`);\n\t\t\tif (oid !== undefined && used.has(oid)) oid = undefined;\n\t\t\tif (oid === undefined) {\n\t\t\t\tconst u = bySource.get(sid);\n\t\t\t\tif (u && !used.has(u)) oid = u;\n\t\t\t}\n\t\t\tif (oid === undefined) {\n\t\t\t\tbyPath ??= oldPaths();\n\t\t\t\tconst p = byPath.get(path);\n\t\t\t\tif (p && !used.has(p)) oid = p;\n\t\t\t}\n\t\t\tif (oid !== undefined) used.add(oid);\n\t\t\tconst o = oid !== undefined ? old.get(oid) : undefined;\n\t\t\tconst id = oid ?? fresh(sid);\n\t\t\tlet n = applyChainProps(copyLayer(s, o, id, newParent), s, enc);\n\t\t\tconst nested = getInstanceData(n);\n\t\t\tif (nested && depth < MAX_DEPTH) {\n\t\t\t\tconst target = nested.componentId;\n\t\t\t\tinstProps.set(id, effectivePropsIn(g, target, nested.properties));\n\t\t\t\tif (getInstanceData(s)?.componentId !== target) {\n\t\t\t\t\tconst main = g.get(target);\n\t\t\t\t\tif (isCanvasComponent(main) && !expanding.includes(target) && !g.isAncestor(target, instId)) {\n\t\t\t\t\t\t// A swapped nested instance shows its own main.\n\t\t\t\t\t\tconst previousComponent = o ? getInstanceData(o)?.componentId : undefined;\n\t\t\t\t\t\tn = applyChainProps(instanceRoot(main!, s, o, id, newParent, sid, nested, previousComponent, false), s, enc);\n\t\t\t\t\t\tout.push(n);\n\t\t\t\t\t\tcopyChildren(target, id, path, [id], [...expanding, target], depth + 1);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tout.push(n);\n\t\t\t\tcopyChildren(sid, id, path, [...enc, id], expanding, depth);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tout.push(n);\n\t\t\tcopyChildren(sid, id, path, enc, expanding, depth);\n\t\t}\n\t};\n\n\tconst root = instanceRoot(comp, inst, inst, instId, inst.parentId, compId, instData, getComponentSourceId(inst), true);\n\tinstProps.set(instId, effectivePropsIn(g, compId, instData.properties));\n\tcopyChildren(compId, instId, '', [instId], [compId], 0);\n\n\t// Foreign content stays where its parent still is.\n\tconst placed = new Set<string>([instId, ...out.map((n) => n.id)]);\n\tconst keptForeign: CanvasNode[] = [];\n\tfor (const id of foreign) {\n\t\tconst n = g.get(id)!;\n\t\tif (n.parentId && placed.has(n.parentId)) {\n\t\t\tplaced.add(id);\n\t\t\tkeptForeign.push(n);\n\t\t}\n\t}\n\tconst removed = new Set<string>([instId, ...oldIds, ...foreign]);\n\tlet block: CanvasNode[] = [root, ...out, ...keptForeign];\n\n\t// A resized instance: its layers follow their constraints (geometry overrides win).\n\tconst compSize = getNodeSize(comp);\n\tconst rootSize = getNodeSize(root);\n\tif (!getAutoLayout(root) && (Math.abs(compSize.width - rootSize.width) > 1e-9 || Math.abs(compSize.height - rootSize.height) > 1e-9)) {\n\t\tconst fixed = new Set(out.filter((n) => getNodeOverrides(n).some((gr) => gr === 'size' || gr === 'position')).map((n) => n.id));\n\t\tconst updates = applyConstraints(block, instId, compSize, opts.layout || {}).filter((u: CanvasNodeUpdate) => !fixed.has(u.id));\n\t\tblock = applyNodeUpdates(block, updates);\n\t}\n\t// Auto layout inside the instance (its ancestors are laid out once per batch).\n\tif (opts.layout) {\n\t\tconst frames = block.filter((n) => getAutoLayout(n)).map((n) => n.id);\n\t\tif (frames.length) block = applyNodeUpdates(block, layoutUpMany(block, frames, opts.layout));\n\t}\n\t// Unchanged layers keep their identity (so a no-op rebuild returns the same array).\n\tblock = block.map((n) => {\n\t\tconst prev = g.get(n.id);\n\t\treturn prev && prev !== n && same(prev, n) ? prev : n;\n\t});\n\treturn { block, removed };\n}\n\n/**\n * Rebuilds the outermost instances containing `ids` against one snapshot of `nodes` (they must\n * not depend on each other), then lays out the auto layout frames around them.\n */\nfunction rebuildBatch(nodes: CanvasNode[], ids: Iterable<string>, opts: ResolvedSync): CanvasNode[] {\n\tconst g = new Graph(nodes);\n\tconst taken = new Set(g.byId.keys());\n\tconst built = new Map<string, BuiltInstance>();\n\tfor (const id of ids) {\n\t\tconst root = g.get(id) ? rootInstanceIn(g, id) ?? id : undefined;\n\t\tif (!root || built.has(root)) continue;\n\t\tconst b = buildInstance(g, root, opts, taken);\n\t\tif (b) built.set(root, b);\n\t}\n\tif (built.size === 0) return nodes;\n\tconst removed = new Set<string>();\n\tfor (const b of built.values()) for (const id of b.removed) removed.add(id);\n\tlet result: CanvasNode[] = [];\n\tfor (const n of nodes) {\n\t\tconst b = built.get(n.id);\n\t\tif (b) result.push(...b.block);\n\t\telse if (!removed.has(n.id)) result.push(n);\n\t}\n\tif (opts.layout) {\n\t\tconst lookup = new Map(result.map((n) => [n.id, n]));\n\t\tconst inFrames = [...built.keys()].filter((r) => {\n\t\t\tconst parent = lookup.get(lookup.get(r)?.parentId ?? '');\n\t\t\treturn !!getAutoLayout(parent);\n\t\t});\n\t\tif (inFrames.length) result = applyNodeUpdates(result, layoutUpMany(result, inFrames, opts.layout));\n\t}\n\treturn result.length === nodes.length && result.every((n, i) => n === nodes[i]) ? nodes : result;\n}\n\n/**\n * Rebuilds every outermost instance of `comps` (and of the components holding them, outwards),\n * inner components first, plus `resync` roots.\n */\nfunction rebuildInstances(nodes: CanvasNode[], comps: Set<string>, resync: Set<string>, opts: ResolvedSync): CanvasNode[] {\n\tlet current = nodes;\n\tif (resync.size) {\n\t\tcurrent = rebuildBatch(current, resync, opts);\n\t\tconst after = new Graph(current);\n\t\tfor (const r of resync) {\n\t\t\tconst c = after.get(r) ? owningComponentIn(after, r) : undefined;\n\t\t\tif (c) comps.add(c);\n\t\t}\n\t}\n\tif (comps.size === 0) return current;\n\tconst g = new Graph(current);\n\tconst rootsOf = new Map<string, Set<string>>();\n\tfor (const n of current) {\n\t\tconst inst = getInstanceData(n);\n\t\tif (!inst) continue;\n\t\tconst root = rootInstanceIn(g, n.id) ?? n.id;\n\t\tlet set = rootsOf.get(inst.componentId);\n\t\tif (!set) rootsOf.set(inst.componentId, (set = new Set()));\n\t\tset.add(root);\n\t}\n\tconst level = new Map<string, number>([...comps].map((c) => [c, 0]));\n\tconst outer = new Map<string, string | undefined>();\n\tlet frontier = [...comps];\n\tfor (let rounds = 0; frontier.length && rounds < 64; rounds++) {\n\t\tconst next: string[] = [];\n\t\tfor (const c of frontier) {\n\t\t\tconst lc = level.get(c) ?? 0;\n\t\t\tfor (const r of rootsOf.get(c) ?? []) {\n\t\t\t\tif (!outer.has(r)) outer.set(r, owningComponentIn(g, r));\n\t\t\t\tconst x = outer.get(r);\n\t\t\t\tif (x && x !== c && (level.get(x) ?? -1) < lc + 1) {\n\t\t\t\t\tlevel.set(x, lc + 1);\n\t\t\t\t\tnext.push(x);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfrontier = next;\n\t}\n\t// Instances inside components by level (each level reads only lower ones), then the rest.\n\tconst batches = new Map<number, string[]>();\n\tfor (const [r, o] of outer) {\n\t\tconst l = o !== undefined ? level.get(o) ?? Number.MAX_SAFE_INTEGER : Number.MAX_SAFE_INTEGER;\n\t\tbatches.set(l, [...(batches.get(l) ?? []), r]);\n\t}\n\tfor (const l of [...batches.keys()].sort((a, b) => a - b)) current = rebuildBatch(current, batches.get(l)!, opts);\n\treturn current;\n}\n\nfunction finish<TNode extends CanvasNode>(original: readonly TNode[], result: CanvasNode[], opts: ResolvedSync): TNode[] {\n\tlet out = result;\n\tif (opts.tokens?.state) out = resolveCanvasTokens(out, opts.tokens.state, opts.tokens);\n\tif (out.length === original.length && out.every((n, i) => n === original[i])) return original as TNode[];\n\treturn out as TNode[];\n}\n\n/**\n * Rebuilds one instance from its main (keeping ids, overrides and property values).\n */\nexport function syncCanvasInstance<TNode extends CanvasNode>(nodes: readonly TNode[], instanceId: string, options: CanvasComponentSyncOptions = {}): TNode[] {\n\tconst opts = resolveSync(options);\n\treturn finish(nodes, rebuildInstances([...nodes], new Set(), new Set([instanceId]), opts), opts);\n}\n\n/**\n * Rebuilds every instance from its main, inner components first (e.g. after loading a\n * document whose instances were saved without their layers).\n */\nexport function syncAllCanvasInstances<TNode extends CanvasNode>(nodes: readonly TNode[], options: CanvasComponentSyncOptions = {}): TNode[] {\n\tconst opts = resolveSync(options);\n\tconst comps = new Set(nodes.filter(isCanvasComponent).map((n) => n.id));\n\treturn finish(nodes, rebuildInstances([...nodes], comps, new Set(), opts), opts);\n}\n\n/** Re-derives the sources and overrides of instance `instId` against its own main by comparing values. */\nfunction rebaseInstance(nodes: CanvasNode[], instId: string, tokens: CanvasTokenOptions | undefined): CanvasNode[] {\n\tconst g = new Graph(nodes);\n\tconst inst = getInstanceData(g.get(instId));\n\tif (!inst) return nodes;\n\tconst compId = inst.componentId;\n\tconst comp = g.get(compId);\n\tconst theirs = new Map<string, string>();\n\tconst mine = new Map<string, string>();\n\tconst paths = (root: string, viaSource: boolean, into: Map<string, string>, flip: boolean) => {\n\t\tconst walk = (parent: string, prefix: string) => {\n\t\t\tconst counts = new Map<string, number>();\n\t\t\tfor (const c of g.children(parent)) {\n\t\t\t\tconst n = g.get(c)!;\n\t\t\t\tconst name = getLayerName((viaSource && g.get(getComponentSourceId(n))) || n);\n\t\t\t\tconst k = counts.get(name) ?? 0;\n\t\t\t\tcounts.set(name, k + 1);\n\t\t\t\tconst path = `${prefix}/${k === 0 ? name : `${name}#${k}`}`;\n\t\t\t\tif (flip) {\n\t\t\t\t\tif (!into.has(path)) into.set(path, c);\n\t\t\t\t} else into.set(c, path);\n\t\t\t\twalk(c, path);\n\t\t\t}\n\t\t};\n\t\twalk(root, '');\n\t};\n\tif (comp) paths(compId, false, theirs, true);\n\tpaths(instId, true, mine, false);\n\tconst updates = new Map<string, CanvasNode>();\n\tfor (const [id, path] of mine) {\n\t\tconst n = g.get(id)!;\n\t\tlet main: string | undefined;\n\t\tlet cur = getComponentSourceId(n);\n\t\tfor (let i = 0; cur && i < MAX_DEPTH; i++) {\n\t\t\tif (cur !== compId && g.get(cur) && g.isAncestor(compId, cur)) {\n\t\t\t\tmain = cur;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcur = getComponentSourceId(g.get(cur));\n\t\t}\n\t\tmain ??= comp ? theirs.get(path) : undefined;\n\t\tconst m = g.get(main);\n\t\tlet next = n;\n\t\tif (m) {\n\t\t\tconst bound = new Set([...propertyDrivenGroups(g, m), ...tokenDrivenGroups(n, tokens)]);\n\t\t\tconst groups = new Set(getNodeOverrides(n).filter((gr) => gr !== 'component' && gr !== 'props'));\n\t\t\tfor (const gr of diffOverrideGroups(m, n)) if (gr !== 'size' && gr !== 'position' && !bound.has(gr)) groups.add(gr);\n\t\t\tif (isCanvasInstance(m) && isCanvasInstance(n) && getInstanceData(m)!.componentId !== getInstanceData(n)!.componentId) groups.add('component');\n\t\t\tnext = setOverrides(withData(n, { ...dataOf(n), [COMPONENT_SOURCE_DATA_KEY]: m.id }), groups);\n\t\t} else next = unlinked(n);\n\t\tif (next !== n) updates.set(id, next);\n\t}\n\tconst root = g.get(instId)!;\n\tlet nextRoot = withData(root, { ...dataOf(root), [COMPONENT_SOURCE_DATA_KEY]: compId });\n\tif (comp) {\n\t\tconst ref = rootReference(g, comp);\n\t\tconst driven = new Set(tokenDrivenGroups(root, tokens));\n\t\tconst groups = new Set(getNodeOverrides(root).filter((gr) => !isOwnRootGroup(gr)));\n\t\tfor (const gr of diffOverrideGroups(ref, root)) if (!isOwnRootGroup(gr) && !driven.has(gr)) groups.add(gr);\n\t\tnextRoot = setOverrides(nextRoot, groups);\n\t}\n\tupdates.set(instId, nextRoot);\n\treturn nodes.map((n) => updates.get(n.id) ?? n);\n}\n\n/** Whether auto layout decides a node's size (it hugs its content, or fills its auto layout parent). */\nfunction sizeFromLayout(g: Graph, node: CanvasNode): boolean {\n\tconst parentFlows = !!getAutoLayout(g.get(node.parentId)) && !getChildLayout(node).absolute;\n\treturn (['width', 'height'] as const).some((axis) => {\n\t\tconst sizing = getLayoutSizing(node, axis);\n\t\treturn (sizing === 'hug' && !!getAutoLayout(node)) || (sizing === 'fill' && parentFlows);\n\t});\n}\n\n/**\n * Keeps components and instances consistent after an edit from `before` to `after`:\n *\n * 1. Layers deleted from inside an instance come back hidden; nodes added inside an instance\n * move out next to it; pasted / duplicated copies of instance layers stop mirroring the main,\n * and a copied nested instance becomes an instance of its own main.\n * 2. Edits to instance layers are recorded as overrides (a value equal to the main's clears\n * the override; values from the same variable or from a component property are not\n * overrides; layer geometry inside an instance is not overridable, the instance's size is).\n * 3. Instances of components whose layers changed are rebuilt (nested components first),\n * as are instances whose main, properties or overrides were switched or reset.\n *\n * Returns `after` unchanged when there is nothing to do, so it can run on every change.\n */\nexport function syncCanvasComponents<TNode extends CanvasNode>(\n\tbefore: readonly TNode[],\n\tafter: readonly TNode[],\n\toptions: CanvasComponentSyncOptions = {}\n): TNode[] {\n\tif (before === after) return after as TNode[];\n\tconst opts = resolveSync(options);\n\tconst prev = new Graph(before);\n\tlet nodes: CanvasNode[] = [...after];\n\tconst resync = new Set<string>();\n\n\t// 1a. Layers deleted inside a surviving instance come back, hidden.\n\tconst afterIds = new Set(after.map((n) => n.id));\n\tconst restore: CanvasNode[] = [];\n\tfor (const b of before) {\n\t\tif (afterIds.has(b.id) || getComponentSourceId(b) === undefined || isCanvasInstance(b) && !enclosingInstanceIn(prev, b.id)) continue;\n\t\tconst root = rootInstanceIn(prev, b.id);\n\t\tif (!root || root === b.id || !afterIds.has(root)) continue;\n\t\trestore.push(b);\n\t}\n\tif (restore.length) {\n\t\tconst restoring = new Set(restore.map((n) => n.id));\n\t\tfor (const b of restore) nodes.push(b.parentId && restoring.has(b.parentId) ? b : writeOverrideGroup(b, 'hidden', true));\n\t\tfor (const b of restore) {\n\t\t\tconst r = rootInstanceIn(prev, b.id);\n\t\t\tif (r) resync.add(r);\n\t\t}\n\t}\n\n\t// 1b. Nodes added inside an existing instance move out; copies brought in elsewhere relink.\n\tlet g = new Graph(nodes);\n\tconst added = nodes.filter((n) => !prev.byId.has(n.id));\n\tif (added.length) {\n\t\tconst addedIds = new Set(added.map((n) => n.id));\n\t\tconst moves = new Map<string, CanvasNode>();\n\t\tfor (const n of added) {\n\t\t\tif (!n.parentId || addedIds.has(n.parentId)) continue;\n\t\t\tconst r = rootInstanceIn(g, n.parentId);\n\t\t\tif (!r || addedIds.has(r)) continue;\n\t\t\tconst rootNode = g.get(r)!;\n\t\t\tconst abs = getAbsolutePosition(n, g.byId);\n\t\t\tconst parent = g.get(rootNode.parentId);\n\t\t\tconst base = parent ? getAbsolutePosition(parent, g.byId) : { x: 0, y: 0 };\n\t\t\tconst moved = { ...n, position: { x: abs.x - base.x, y: abs.y - base.y } } as Data;\n\t\t\tif (rootNode.parentId) moved.parentId = rootNode.parentId;\n\t\t\telse delete moved.parentId;\n\t\t\tmoves.set(n.id, moved as CanvasNode);\n\t\t}\n\t\tif (moves.size) {\n\t\t\tnodes = nodes.map((n) => moves.get(n.id) ?? n);\n\t\t\tg = new Graph(nodes);\n\t\t}\n\t\tconst rebase: string[] = [];\n\t\tconst strips = new Map<string, CanvasNode>();\n\t\tfor (const n of added) {\n\t\t\tconst cur = g.get(n.id)!;\n\t\t\tif (isCanvasInstance(cur)) {\n\t\t\t\tif (enclosingInstanceIn(g, n.id) === undefined && getComponentSourceId(cur) !== getInstanceData(cur)!.componentId) rebase.push(n.id);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (getComponentSourceId(cur) === undefined && getNodeOverrides(cur).length === 0) continue;\n\t\t\tconst r = rootInstanceIn(g, n.id);\n\t\t\tif (!r || !addedIds.has(r) || moves.has(n.id)) strips.set(n.id, unlinked(cur, true));\n\t\t}\n\t\tif (strips.size) nodes = nodes.map((n) => strips.get(n.id) ?? n);\n\t\tfor (const id of rebase) nodes = rebaseInstance(nodes, id, opts.tokens);\n\t\tg = new Graph(nodes);\n\t}\n\n\t// 2. Overrides of edited instance layers.\n\tconst overrideUpdates = new Map<string, CanvasNode>();\n\tfor (const n of nodes) {\n\t\tconst b = prev.get(n.id);\n\t\tif (!b || b === n) continue;\n\t\tconst inst = owningInstanceIn(g, n.id);\n\t\tif (!inst || (getComponentSourceId(n) === undefined && inst !== n.id)) continue;\n\t\tconst topRoot = inst === n.id && enclosingInstanceIn(g, n.id) === undefined;\n\t\tconst bi = getInstanceData(b);\n\t\tconst ni = getInstanceData(n);\n\t\tconst raw = diffOverrideGroups(b, n);\n\t\tif (topRoot) {\n\t\t\tif (bi && ni && (bi.componentId !== ni.componentId || !same(bi.properties ?? {}, ni.properties ?? {}))) resync.add(n.id);\n\t\t\tif (raw.includes('size')) resync.add(n.id);\n\t\t}\n\t\tlet groups = raw.filter((gr) => gr !== 'position' && (gr !== 'size' || (topRoot && !sizeFromLayout(g, n))));\n\t\tif (topRoot) groups = groups.filter((gr) => !isOwnRootGroup(gr));\n\t\tlet cleared: string[] = [];\n\t\tconst srcId = topRoot ? ni?.componentId : getComponentSourceId(n);\n\t\tconst src = g.get(srcId);\n\t\tif (src) {\n\t\t\tconst reference = topRoot ? rootReference(g, src) : src;\n\t\t\tconst differs = new Set(diffOverrideGroups(reference, n));\n\t\t\tconst driven = new Set(tokenDrivenGroups(n, opts.tokens));\n\t\t\tgroups = groups.filter((gr) => differs.has(gr) && !driven.has(gr));\n\t\t\tcleared = raw.filter((gr) => gr !== 'size' && gr !== 'position' && !differs.has(gr) && getNodeOverrides(n).includes(gr));\n\t\t\tif (topRoot) cleared = cleared.filter((gr) => !isOwnRootGroup(gr));\n\t\t}\n\t\tconst lost = getNodeOverrides(b).some((gr) => !getNodeOverrides(n).includes(gr));\n\t\tconst nestedSwitch = !topRoot && raw.some((gr) => gr === 'component' || gr === 'props');\n\t\tif (groups.length || cleared.length) {\n\t\t\tconst set = new Set(getNodeOverrides(n));\n\t\t\tfor (const gr of groups) set.add(gr);\n\t\t\tfor (const gr of cleared) set.delete(gr);\n\t\t\tconst updated = setOverrides(n, set);\n\t\t\tif (updated !== n) overrideUpdates.set(n.id, updated);\n\t\t}\n\t\tif (lost || nestedSwitch) {\n\t\t\tconst r = rootInstanceIn(g, n.id);\n\t\t\tif (r) resync.add(r);\n\t\t}\n\t}\n\tif (overrideUpdates.size) {\n\t\tnodes = nodes.map((n) => overrideUpdates.get(n.id) ?? n);\n\t\tg = new Graph(nodes);\n\t}\n\n\t// 3. Components whose layers changed.\n\tconst comps = new Set<string>();\n\tfor (const n of nodes) {\n\t\tconst b = prev.get(n.id);\n\t\tif (b === n) continue;\n\t\tconst c = owningComponentIn(g, n.id);\n\t\tif (isCanvasComponentSet(n) && (!b || !same(dataOf(b)[COMPONENT_SET_DATA_KEY], dataOf(n)[COMPONENT_SET_DATA_KEY]))) {\n\t\t\tfor (const v of variantsIn(g, n.id)) comps.add(v.id);\n\t\t}\n\t\tif (!c) continue;\n\t\tif (!b) {\n\t\t\tcomps.add(c);\n\t\t\tcontinue;\n\t\t}\n\t\tconst structural =\n\t\t\tb.parentId !== n.parentId ||\n\t\t\t!same(dataOf(b)[COMPONENT_DATA_KEY], dataOf(n)[COMPONENT_DATA_KEY]) ||\n\t\t\t!same(getComponentPropertyRefs(b), getComponentPropertyRefs(n)) ||\n\t\t\tgetComponentSourceId(b) !== getComponentSourceId(n) ||\n\t\t\t!same(getNodeOverrides(b), getNodeOverrides(n));\n\t\tconst diff = diffOverrideGroups(b, n);\n\t\tif (!structural && (diff.length === 0 || (n.id === c && diff.every(isOwnRootGroup)))) continue;\n\t\tcomps.add(c);\n\t}\n\tfor (const b of before) {\n\t\tif (afterIds.has(b.id)) continue;\n\t\tconst c = owningComponentIn(prev, b.id);\n\t\tif (c && c !== b.id && g.get(c)) comps.add(c);\n\t}\n\t// Order changes among a component's layers (array order is layer order).\n\t{\n\t\tconst order = (list: readonly CanvasNode[]) => {\n\t\t\tconst m = new Map<string, string[]>();\n\t\t\tfor (const n of list) if (n.parentId) m.set(n.parentId, [...(m.get(n.parentId) ?? []), n.id]);\n\t\t\treturn m;\n\t\t};\n\t\tconst ob = order(before);\n\t\tconst oa = order(nodes);\n\t\tfor (const [parent, kids] of oa) {\n\t\t\tif (same(ob.get(parent), kids)) continue;\n\t\t\tconst c = owningComponentIn(g, parent);\n\t\t\tif (c && !owningInstanceIn(g, parent)) comps.add(c);\n\t\t}\n\t}\n\n\tnodes = rebuildInstances(nodes, comps, resync, opts);\n\treturn finish(after, nodes, opts);\n}\n\n// ---------------------------------------------------------------------------------------------\n// Operations\n\nfunction fail<TNode extends CanvasNode>(nodes: readonly TNode[], code: CanvasComponentErrorCode, reason: string): CanvasComponentResult<TNode> {\n\treturn { ok: false, nodes: nodes as TNode[], code, reason };\n}\n\nfunction done<TNode extends CanvasNode>(\n\tbefore: readonly TNode[],\n\tafter: CanvasNode[],\n\tids: string[],\n\toptions: CanvasComponentSyncOptions\n): CanvasComponentResult<TNode> {\n\treturn { ok: true, nodes: syncCanvasComponents(before, after as TNode[], options), ids };\n}\n\nfunction uniqueId(base: string, taken: ReadonlySet<string> | Map<string, unknown>): string {\n\tlet id = base;\n\tfor (let k = 2; taken.has(id); k++) id = `${base}-${k}`;\n\treturn id;\n}\n\nfunction sizedStyle(like: CanvasNode['style'], width: number, height: number): CanvasNode['style'] {\n\treturn typeof like === 'string' ? `width: ${width}px; height: ${height}px` : { width, height };\n}\n\n/** Wraps `ids` (siblings) in a new frame node at their bounds (+ `padding`), returning the new nodes. */\nfunction wrapInFrame(nodes: CanvasNode[], ids: string[], frame: { id: string; type: string; data: Data; padding: number }): CanvasNode[] {\n\tconst g = new Graph(nodes);\n\tconst roots = ids.map((id) => g.get(id)!);\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tfor (const n of roots) {\n\t\tconst { width, height } = getNodeSize(n);\n\t\tminX = Math.min(minX, n.position.x);\n\t\tminY = Math.min(minY, n.position.y);\n\t\tmaxX = Math.max(maxX, n.position.x + width);\n\t\tmaxY = Math.max(maxY, n.position.y + height);\n\t}\n\tminX -= frame.padding;\n\tminY -= frame.padding;\n\tmaxX += frame.padding;\n\tmaxY += frame.padding;\n\tconst width = maxX - minX;\n\tconst height = maxY - minY;\n\tconst parentId = roots[0].parentId;\n\tconst wrapper: CanvasNode = {\n\t\tid: frame.id,\n\t\ttype: frame.type,\n\t\tposition: { x: minX, y: minY },\n\t\twidth,\n\t\theight,\n\t\tstyle: sizedStyle(roots[0].style, width, height),\n\t\tdata: frame.data,\n\t\t...(parentId ? { parentId } : {})\n\t};\n\tconst selected = new Set(ids);\n\tconst firstIndex = nodes.findIndex((n) => selected.has(n.id));\n\tconst out: CanvasNode[] = [];\n\tnodes.forEach((n, i) => {\n\t\tif (i === firstIndex) out.push(wrapper);\n\t\tout.push(selected.has(n.id) ? { ...n, parentId: frame.id, position: { x: n.position.x - minX, y: n.position.y - minY } } : n);\n\t});\n\treturn out;\n}\n\n/**\n * Create Component: a single frame-like node (one with children, a frame / group type or auto\n * layout) becomes a component; anything else is wrapped in a new component frame. Refuses\n * layers inside an instance and layers that don't share a parent.\n */\nexport function createCanvasComponent<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tids: Iterable<string>,\n\toptions: CreateCanvasComponentOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst picked = [...new Set(ids)].filter((id) => g.get(id));\n\tconst roots = picked.filter((id) => !picked.some((o) => o !== id && g.isAncestor(o, id)));\n\tif (roots.length === 0) return fail(nodes, 'not-found', 'Select the layers to make a component.');\n\tif (roots.some((id) => enclosingInstanceIn(g, id) !== undefined)) {\n\t\treturn fail(nodes, 'inside-instance', \"Layers inside an instance can't become components: detach the instance first.\");\n\t}\n\tif (roots.some((id) => isCanvasComponent(g.get(id)) || isCanvasComponentSet(g.get(id)))) {\n\t\treturn fail(nodes, 'invalid', 'The selection is already a component.');\n\t}\n\tif (new Set(roots.map((id) => g.get(id)!.parentId ?? '')).size > 1) return fail(nodes, 'invalid', 'Select layers that share a parent.');\n\tconst frameTypes = options.frameTypes ?? ['group', 'frame'];\n\tif (roots.length === 1) {\n\t\tconst node = g.get(roots[0])!;\n\t\tconst frameLike = !isCanvasInstance(node) && (g.children(node.id).length > 0 || (typeof node.type === 'string' && frameTypes.includes(node.type)) || !!getAutoLayout(node));\n\t\tif (frameLike) {\n\t\t\tconst after = nodes.map((n) => (n.id === node.id ? withData(n, { ...dataOf(n), [COMPONENT_DATA_KEY]: {} }) : n));\n\t\t\treturn done(nodes, after, [node.id], options);\n\t\t}\n\t}\n\tconst id = options.id ?? uniqueId('component', g.byId);\n\tconst name = options.name ?? (roots.length === 1 ? getLayerName(g.get(roots[0])) || 'Component' : 'Component');\n\tconst after = wrapInFrame([...nodes], roots, { id, type: options.frameType ?? 'group', data: { label: name, [COMPONENT_DATA_KEY]: {} }, padding: 0 });\n\treturn done(nodes, after, [id], options);\n}\n\n/**\n * Combine as Variants: wraps sibling components in a component set. Each variant takes its\n * `data.component.variant`, else the `Prop=Value` pairs of its name, else `Property 1=…`.\n */\nexport function combineCanvasComponentsAsVariants<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tids: Iterable<string>,\n\toptions: CanvasComponentSyncOptions & { id?: string; name?: string; frameType?: string; padding?: number } = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst picked = [...new Set(ids)].filter((id) => g.get(id));\n\tif (picked.length === 0) return fail(nodes, 'not-found', 'Select components to combine.');\n\tif (!picked.every((id) => isCanvasComponent(g.get(id)))) return fail(nodes, 'invalid', 'Select components only.');\n\tif (picked.some((id) => variantSetIn(g, id))) return fail(nodes, 'invalid', 'A component is already a variant of a set.');\n\tif (picked.some((id) => owningInstanceIn(g, id))) return fail(nodes, 'inside-instance', \"Components inside an instance can't be combined.\");\n\tif (new Set(picked.map((id) => g.get(id)!.parentId ?? '')).size > 1) return fail(nodes, 'invalid', 'Select components that share a parent.');\n\tconst ordered = nodes.filter((n) => picked.includes(n.id)).map((n) => n.id);\n\tconst first = g.get(ordered[0])!;\n\tconst firstName = getLayerName(first);\n\tconst name = options.name ?? (firstName && !firstName.includes('=') ? firstName.split('/')[0].trim() : 'Component');\n\tconst id = options.id ?? uniqueId('component-set', g.byId);\n\tlet after = wrapInFrame([...nodes], ordered, {\n\t\tid,\n\t\ttype: options.frameType ?? 'group',\n\t\tdata: { label: name, [COMPONENT_SET_DATA_KEY]: {} },\n\t\tpadding: options.padding ?? 20\n\t});\n\t// Property order: as first seen in the variants' names.\n\tconst order: string[] = [];\n\tfor (const vid of ordered) for (const k of Object.keys(getVariantValues(g.get(vid)))) if (!order.includes(k)) order.push(k);\n\tafter = after.map((n) => {\n\t\tconst k = ordered.indexOf(n.id);\n\t\tif (k < 0) return n;\n\t\tlet variant = getVariantValues(n);\n\t\tif (Object.keys(variant).length === 0) variant = { 'Property 1': k === 0 ? 'Default' : `Variant${k + 1}` };\n\t\tconst comp = { ...(getComponentData(n) ?? {}), variant };\n\t\treturn withData(n, { ...dataOf(n), [COMPONENT_DATA_KEY]: comp, label: formatVariantName(variant, order) });\n\t});\n\treturn done(nodes, after, [id], options);\n}\n\n/**\n * Create Instance of a component (or of a set's default variant). Refuses placing it inside\n * its own main (directly or through nested instances) or inside another instance.\n */\nexport function createCanvasInstance<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tcomponentId: string,\n\toptions: CreateCanvasInstanceOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst main = mainOfIn(g, componentId);\n\tif (!main) return fail(nodes, 'not-found', 'Choose a component or component set.');\n\tconst parentId = options.parentId;\n\tif (parentId !== undefined && !g.get(parentId)) return fail(nodes, 'not-found', `Unknown parent \"${parentId}\".`);\n\tif (createsCycleIn(g, main, parentId)) return fail(nodes, 'cycle', \"An instance can't be placed inside its own main component.\");\n\tif (parentId !== undefined && owningInstanceIn(g, parentId)) return fail(nodes, 'inside-instance', \"Instances can't gain layers: place it outside the instance.\");\n\tconst comp = g.get(main)!;\n\tconst id = options.id ?? uniqueId(`${main}-instance`, g.byId);\n\tif (g.get(id)) return fail(nodes, 'invalid', `Id \"${id}\" is taken.`);\n\tlet position = options.position;\n\tif (!position) {\n\t\tif (parentId !== undefined) position = { x: 0, y: 0 };\n\t\telse {\n\t\t\tconst top = g.chain(main).pop()!;\n\t\t\tposition = { x: getAbsolutePosition(top, g.byId).x + getNodeSize(top).width + 40, y: getAbsolutePosition(comp, g.byId).y };\n\t\t}\n\t}\n\tconst root: CanvasNode = {\n\t\tid,\n\t\ttype: comp.type,\n\t\tposition: { ...position },\n\t\tdata: { [INSTANCE_DATA_KEY]: { componentId: main, properties: effectivePropsIn(g, main, undefined) }, [COMPONENT_SOURCE_DATA_KEY]: main },\n\t\t...(parentId !== undefined ? { parentId } : {})\n\t};\n\tconst opts = resolveSync(options);\n\tlet after = rebuildBatch([...nodes, root], [id], opts);\n\tif (options.properties && Object.keys(options.properties).length) {\n\t\tconst set = setCanvasInstanceProperties(after, [id], options.properties, { ...options, tokens: undefined });\n\t\tif (!set.ok) return fail(nodes, set.code, set.reason);\n\t\tafter = set.nodes;\n\t}\n\treturn { ok: true, nodes: finish(nodes, after, opts), ids: [id] };\n}\n\n/**\n * Detach Instance: the instance becomes a plain frame with the same look; its layers stop\n * mirroring the main, and instances nested in it become instances of their own mains. Nested\n * instances can't be detached on their own.\n */\nexport function detachCanvasInstance<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tids: Iterable<string>,\n\toptions: CanvasComponentSyncOptions = {}\n): CanvasComponentResult<TNode> {\n\tlet current: CanvasNode[] = [...nodes];\n\tconst doneIds: string[] = [];\n\tfor (const id of new Set(ids)) {\n\t\tconst g = new Graph(current);\n\t\tif (!isCanvasInstance(g.get(id))) continue;\n\t\tif (enclosingInstanceIn(g, id) !== undefined) {\n\t\t\treturn fail(nodes, 'nested-instance', \"A nested instance can't be detached on its own: detach the outer instance first.\");\n\t\t}\n\t\tconst nested: string[] = [];\n\t\tconst updates = new Map<string, CanvasNode>();\n\t\tfor (const d of g.subtree(id)) {\n\t\t\tif (d === id || enclosingInstanceIn(g, d) !== id) continue;\n\t\t\tif (isCanvasInstance(g.get(d))) nested.push(d);\n\t\t\telse updates.set(d, unlinked(g.get(d)!));\n\t\t}\n\t\tconst root = g.get(id)!;\n\t\tconst data = { ...dataOf(root) };\n\t\tdelete data[INSTANCE_DATA_KEY];\n\t\tdelete data[COMPONENT_SOURCE_DATA_KEY];\n\t\tdelete data[COMPONENT_OVERRIDES_DATA_KEY];\n\t\tupdates.set(id, withData(root, data));\n\t\tcurrent = current.map((n) => updates.get(n.id) ?? n);\n\t\tfor (const nn of nested) current = rebaseInstance(current, nn, options.tokens);\n\t\tdoneIds.push(id);\n\t}\n\tif (doneIds.length === 0) return fail(nodes, 'invalid', 'Select an instance.');\n\treturn done(nodes, current, doneIds, options);\n}\n\n/**\n * Reset overrides: `group` resets one override group of layer `id`; without it, an instance\n * resets every override in it (its own size stays) and its property values, a layer resets all of\n * its own.\n */\nexport function resetCanvasInstanceOverrides<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tid: string,\n\tgroup?: string,\n\toptions: CanvasComponentSyncOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst node = g.get(id);\n\tconst inst = node ? owningInstanceIn(g, id) : undefined;\n\tif (!node || !inst) return fail(nodes, 'not-found', 'Select an instance or a layer inside one.');\n\tconst top = inst === id && enclosingInstanceIn(g, id) === undefined;\n\tconst updates = new Map<string, CanvasNode>();\n\tconst resetProps = (n: CanvasNode): CanvasNode => {\n\t\tconst data = getInstanceData(n)!;\n\t\tconst defaults = effectivePropsIn(g, data.componentId, undefined);\n\t\treturn same(data.properties ?? {}, defaults) ? n : withData(n, { ...dataOf(n), [INSTANCE_DATA_KEY]: { ...data, properties: defaults } });\n\t};\n\tif (group !== undefined) {\n\t\tlet next = setOverrides(node, getNodeOverrides(node).filter((gr) => gr !== group));\n\t\tif (top && group === 'props') next = resetProps(next);\n\t\tupdates.set(id, next);\n\t} else if (inst === id) {\n\t\tfor (const d of g.subtree(id)) {\n\t\t\tconst n = g.get(d)!;\n\t\t\tconst keep = d === id && top ? getNodeOverrides(n).filter((gr) => gr === 'size') : [];\n\t\t\tupdates.set(d, setOverrides(n, keep));\n\t\t}\n\t\tif (top) updates.set(id, resetProps(updates.get(id)!));\n\t} else updates.set(id, setOverrides(node, []));\n\tconst after = nodes.map((n) => updates.get(n.id) ?? n);\n\tconst changed = after.some((n, i) => n !== nodes[i]);\n\tif (!changed) return { ok: true, nodes: nodes as TNode[], ids: [] };\n\tconst synced = syncCanvasComponents(nodes, after as TNode[], options);\n\treturn { ok: true, nodes: synced, ids: [inst] };\n}\n\n/**\n * Push overrides to main: copies every overridden value of the (outermost) instance to the\n * main layers it mirrors (geometry, swaps and property values stay with the instance), clears\n * those overrides and updates every other instance.\n */\nexport function pushCanvasInstanceOverrides<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tid: string,\n\toptions: CanvasComponentSyncOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst inst = g.get(id) ? rootInstanceIn(g, id) : undefined;\n\tif (!inst) return fail(nodes, 'not-found', 'Select an instance.');\n\tconst compId = getInstanceData(g.get(inst))!.componentId;\n\tif (!isCanvasComponent(g.get(compId))) return fail(nodes, 'missing-main', 'The main component was deleted: restore it first.');\n\tconst updates = new Map<string, CanvasNode>();\n\tlet pushed = 0;\n\tfor (const d of g.subtree(inst)) {\n\t\tconst n = g.get(d)!;\n\t\tconst overrides = getNodeOverrides(n);\n\t\tif (overrides.length === 0) continue;\n\t\tconst src = d === inst ? compId : getComponentSourceId(n);\n\t\tif (!src || !g.get(src) || (src !== compId && !g.isAncestor(compId, src))) continue;\n\t\tconst groups = overrides.filter(\n\t\t\t(gr) => gr !== 'size' && gr !== 'position' && gr !== 'component' && gr !== 'props' && !(d === inst && (gr === 'data.label' || gr === 'data.name' || isOwnRootGroup(gr)))\n\t\t);\n\t\tif (groups.length === 0) continue;\n\t\tpushed++;\n\t\tupdates.set(src, copyGroups(n, updates.get(src) ?? g.get(src)!, groups));\n\t\tupdates.set(d, setOverrides(n, overrides.filter((gr) => !groups.includes(gr))));\n\t}\n\tif (pushed === 0) return fail(nodes, 'no-overrides', 'The instance has no changes to push.');\n\tconst after = nodes.map((n) => updates.get(n.id) ?? n);\n\treturn done(nodes, after, [inst], options);\n}\n\n/** Swaps instances to another component (or a set's default variant), keeping same-named property values. */\nexport function swapCanvasInstance<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tids: Iterable<string>,\n\tcomponentId: string,\n\toptions: CanvasComponentSyncOptions & { keepTextOverridesOnly?: boolean } = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst main = mainOfIn(g, componentId);\n\tif (!main) return fail(nodes, 'not-found', 'Choose a component or component set.');\n\tconst updates = new Map<string, CanvasNode>();\n\tconst list = [...new Set(ids)];\n\tfor (const id of list) {\n\t\tconst n = g.get(id);\n\t\tconst inst = getInstanceData(n);\n\t\tif (!n || !inst) return fail(nodes, 'invalid', `\"${id}\" is not an instance.`);\n\t\tif (g.isAncestor(main, id) || createsCycleIn(g, main, n.parentId)) return fail(nodes, 'cycle', \"An instance can't show the component it is inside.\");\n\t\tconst defaults = effectivePropsIn(g, main, undefined);\n\t\tconst properties: Record<string, Value> = {};\n\t\tfor (const [k, d] of Object.entries(defaults)) properties[k] = inst.properties?.[k] !== undefined && typeof inst.properties[k] === typeof d ? inst.properties[k] : d;\n\t\tupdates.set(id, withData(n, { ...dataOf(n), [INSTANCE_DATA_KEY]: { ...inst, componentId: main, properties } }));\n\t\tif (options.keepTextOverridesOnly) {\n\t\t\tfor (const d of g.subtree(id)) {\n\t\t\t\tconst layer = updates.get(d) ?? g.get(d)!;\n\t\t\t\tconst text = typeof layer.type === 'string' && (options.textTypes ?? ['text']).includes(layer.type);\n\t\t\t\tconst keep = getNodeOverrides(layer).filter((gr) => text || (d === id && gr === 'size'));\n\t\t\t\tupdates.set(d, setOverrides(layer, keep));\n\t\t\t}\n\t\t}\n\t}\n\tif (list.length === 0) return fail(nodes, 'invalid', 'Select an instance.');\n\tconst after = nodes.map((n) => updates.get(n.id) ?? n);\n\treturn done(nodes, after, list, options);\n}\n\n/**\n * Sets instance property values: variant properties switch to the matching variant of the set\n * (keeping compatible overrides), others must be defined on the component with the right type\n * (boolean, text, or a component id for instance swap).\n */\nexport function setCanvasInstanceProperties<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tids: Iterable<string>,\n\tproperties: Readonly<Record<string, unknown>>,\n\toptions: CanvasComponentSyncOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst updates = new Map<string, CanvasNode>();\n\tconst list = [...new Set(ids)];\n\tif (list.length === 0) return fail(nodes, 'invalid', 'Select an instance.');\n\tfor (const id of list) {\n\t\tconst n = g.get(id);\n\t\tconst inst = getInstanceData(n);\n\t\tif (!n || !inst) return fail(nodes, 'invalid', `\"${id}\" is not an instance.`);\n\t\tconst set = variantSetIn(g, inst.componentId);\n\t\tconst options_ = set ? variantOptionsIn(g, set.id) : [];\n\t\tconst wanted = { ...getVariantValues(g.get(inst.componentId)) };\n\t\tconst rest: [string, unknown][] = [];\n\t\tfor (const [k, v] of Object.entries(properties)) {\n\t\t\tconst option = options_.find((o) => o.name === k);\n\t\t\tif (option) {\n\t\t\t\tconst value = typeof v === 'string' ? v : String(v);\n\t\t\t\tif (!option.values.includes(value)) return fail(nodes, 'invalid', `\"${k}\" must be one of ${option.values.join(', ')}.`);\n\t\t\t\twanted[k] = value;\n\t\t\t} else rest.push([k, v]);\n\t\t}\n\t\tlet target = inst.componentId;\n\t\tif (set && !same(wanted, getVariantValues(g.get(inst.componentId)))) target = findVariantIn(g, set.id, wanted) ?? target;\n\t\tconst defs = propertyDefsIn(g, target);\n\t\tconst values: Record<string, Value> = { ...(inst.properties ?? {}) };\n\t\tfor (const [k, v] of rest) {\n\t\t\tconst def = defs[k];\n\t\t\tif (!def) {\n\t\t\t\tconst known = [...Object.keys(defs), ...options_.map((o) => o.name)];\n\t\t\t\treturn fail(nodes, 'unknown-property', `Unknown property \"${k}\"${known.length ? ` (known: ${known.join(', ')})` : ''}.`);\n\t\t\t}\n\t\t\tif (def.type === 'boolean') {\n\t\t\t\tconst b = typeof v === 'boolean' ? v : v === 'true' ? true : v === 'false' ? false : undefined;\n\t\t\t\tif (b === undefined) return fail(nodes, 'type-mismatch', `\"${k}\" takes true or false.`);\n\t\t\t\tvalues[k] = b;\n\t\t\t} else if (def.type === 'text') {\n\t\t\t\tif (typeof v !== 'string' && typeof v !== 'number') return fail(nodes, 'type-mismatch', `\"${k}\" takes text.`);\n\t\t\t\tvalues[k] = String(v);\n\t\t\t} else {\n\t\t\t\tconst main = typeof v === 'string' ? mainOfIn(g, v) : undefined;\n\t\t\t\tif (!main) return fail(nodes, 'type-mismatch', `\"${k}\" takes a component id.`);\n\t\t\t\tif (main === target || componentDepsIn(g, main).has(target) || createsCycleIn(g, main, n.parentId)) {\n\t\t\t\t\treturn fail(nodes, 'cycle', `\"${k}\" can't show a component that contains this one.`);\n\t\t\t\t}\n\t\t\t\tvalues[k] = main;\n\t\t\t}\n\t\t}\n\t\tconst properties2 = effectivePropsIn(g, target, values);\n\t\tupdates.set(id, withData(n, { ...dataOf(n), [INSTANCE_DATA_KEY]: { ...inst, componentId: target, properties: properties2 } }));\n\t}\n\tconst after = nodes.map((n) => updates.get(n.id) ?? n);\n\treturn done(nodes, after, list, options);\n}\n\n/**\n * Adds, edits (`def`) or deletes (`null`) a component property on a component (variants share\n * their set's). `newName` renames it, carrying layer bindings and instance values along.\n */\nexport function setCanvasComponentProperty<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tcomponentId: string,\n\tname: string,\n\tdef: CanvasComponentPropertyDef | null,\n\toptions: CanvasComponentSyncOptions & { newName?: string } = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst target = g.get(componentId);\n\tconst compId = isCanvasComponentSet(target) ? undefined : owningComponentIn(g, componentId);\n\tconst owner = isCanvasComponentSet(target) ? target! : compId ? variantSetIn(g, compId) ?? g.get(compId)! : undefined;\n\tif (!owner) return fail(nodes, 'not-found', 'Select a component or component set.');\n\tconst key = isCanvasComponentSet(owner) ? COMPONENT_SET_DATA_KEY : COMPONENT_DATA_KEY;\n\tconst ownerData = { ...(dataOf(owner)[key] ?? {}) };\n\tconst defs: Record<string, CanvasComponentPropertyDef> = { ...(ownerData.properties ?? {}) };\n\tconst finalName = (options.newName ?? name).trim();\n\tif (!finalName) return fail(nodes, 'invalid', 'Property names must not be empty.');\n\tif (isCanvasComponentSet(owner) && variantOptionsIn(g, owner.id).some((o) => o.name === finalName)) {\n\t\treturn fail(nodes, 'invalid', `\"${finalName}\" is a variant property.`);\n\t}\n\tif (finalName !== name && defs[finalName]) return fail(nodes, 'invalid', `\"${finalName}\" exists already.`);\n\tif (def === null) {\n\t\tif (!defs[name]) return fail(nodes, 'unknown-property', `Unknown property \"${name}\".`);\n\t\tdelete defs[name];\n\t} else {\n\t\tif (def.type === 'boolean' && typeof def.default !== 'boolean') return fail(nodes, 'type-mismatch', 'A boolean property needs a boolean default.');\n\t\tif (def.type === 'text' && typeof def.default !== 'string') return fail(nodes, 'type-mismatch', 'A text property needs a text default.');\n\t\tif (def.type === 'instanceSwap' && !mainOfIn(g, def.default)) return fail(nodes, 'type-mismatch', 'An instance swap property needs a component as default.');\n\t\tif (finalName !== name) delete defs[name];\n\t\tdefs[finalName] = def;\n\t}\n\townerData.properties = defs;\n\tconst comps = isCanvasComponentSet(owner) ? variantsIn(g, owner.id).map((v) => v.id) : [owner.id];\n\tconst updates = new Map<string, CanvasNode>();\n\tupdates.set(owner.id, withData(owner, { ...dataOf(owner), [key]: ownerData }));\n\tconst renamed = def === null ? null : finalName;\n\tif (renamed !== name) {\n\t\tfor (const c of comps) {\n\t\t\tfor (const d of g.subtree(c)) {\n\t\t\t\tconst n = updates.get(d) ?? g.get(d)!;\n\t\t\t\tconst refs = { ...getComponentPropertyRefs(n) };\n\t\t\t\tlet touched = false;\n\t\t\t\tfor (const [field, prop] of Object.entries(refs)) {\n\t\t\t\t\tif (prop !== name) continue;\n\t\t\t\t\ttouched = true;\n\t\t\t\t\tif (renamed === null) delete refs[field];\n\t\t\t\t\telse refs[field] = renamed;\n\t\t\t\t}\n\t\t\t\tif (!touched) continue;\n\t\t\t\tconst data: Data = { ...dataOf(n), [COMPONENT_PROPERTY_REFS_DATA_KEY]: refs };\n\t\t\t\tif (Object.keys(refs).length === 0) delete data[COMPONENT_PROPERTY_REFS_DATA_KEY];\n\t\t\t\tupdates.set(d, withData(n, data));\n\t\t\t}\n\t\t}\n\t\tfor (const n of nodes) {\n\t\t\tconst inst = getInstanceData(n);\n\t\t\tif (!inst || !comps.includes(inst.componentId) || !inst.properties || !(name in inst.properties)) continue;\n\t\t\tconst props = { ...inst.properties };\n\t\t\tconst v = props[name];\n\t\t\tdelete props[name];\n\t\t\tif (renamed !== null) props[renamed] = v;\n\t\t\tconst cur = updates.get(n.id) ?? n;\n\t\t\tupdates.set(n.id, withData(cur, { ...dataOf(cur), [INSTANCE_DATA_KEY]: { ...inst, properties: props } }));\n\t\t}\n\t}\n\tconst after = nodes.map((n) => updates.get(n.id) ?? n);\n\treturn done(nodes, after, [owner.id], options);\n}\n\n/**\n * Binds a field of a main-component layer to a component property (`null` unbinds): `visible`\n * takes a boolean property, `data.<key>` a text property, `component` (on a nested instance) an\n * instance swap property.\n */\nexport function bindCanvasComponentProperty<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tlayerId: string,\n\tfield: CanvasComponentPropertyField,\n\tname: string | null,\n\toptions: CanvasComponentSyncOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst layer = g.get(layerId);\n\tconst compId = layer ? owningComponentIn(g, layerId) : undefined;\n\tif (!layer || !compId || compId === layerId) return fail(nodes, 'not-found', 'Select a layer of a main component.');\n\tconst inst = enclosingInstanceIn(g, layerId);\n\tif (inst && g.isAncestor(compId, inst)) return fail(nodes, 'inside-instance', \"Layers of a nested instance can't be bound: bind the nested instance's own main.\");\n\tconst refs = { ...getComponentPropertyRefs(layer) };\n\tif (name === null) delete refs[field];\n\telse {\n\t\tconst def = propertyDefsIn(g, compId)[name];\n\t\tif (!def) return fail(nodes, 'unknown-property', `Unknown property \"${name}\".`);\n\t\tconst ok =\n\t\t\t(def.type === 'boolean' && field === 'visible') ||\n\t\t\t(def.type === 'text' && field.startsWith('data.')) ||\n\t\t\t(def.type === 'instanceSwap' && field === 'component' && isCanvasInstance(layer));\n\t\tif (!ok) return fail(nodes, 'type-mismatch', `A ${def.type} property can't drive \"${field}\".`);\n\t\trefs[field] = name;\n\t}\n\tconst data = { ...dataOf(layer) };\n\tif (Object.keys(refs).length) data[COMPONENT_PROPERTY_REFS_DATA_KEY] = refs;\n\telse delete data[COMPONENT_PROPERTY_REFS_DATA_KEY];\n\tconst after = nodes.map((n) => (n.id === layerId ? withData(n, data) : n));\n\treturn done(nodes, after, [layerId], options);\n}\n\n/**\n * Restore Main Component of an instance whose main was deleted: the instance becomes the main\n * again, with its old id, and its layers take the ids their copies mirror, so every other\n * instance re-links. Its property values become the property defaults.\n */\nexport function restoreCanvasMainComponent<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tid: string,\n\toptions: CanvasComponentSyncOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst inst = getInstanceData(g.get(id));\n\tif (!inst) return fail(nodes, 'invalid', 'Select an instance.');\n\tif (g.get(inst.componentId)) return fail(nodes, 'invalid', 'The main component still exists.');\n\tif (enclosingInstanceIn(g, id) !== undefined) return fail(nodes, 'nested-instance', 'Select the outer instance.');\n\tconst compId = inst.componentId;\n\tconst sub = g.subtree(id);\n\tconst map = new Map<string, string>([[id, compId]]);\n\tconst taken = new Set(nodes.map((n) => n.id));\n\ttaken.add(compId);\n\tfor (const d of sub) {\n\t\tif (d === id) continue;\n\t\tconst src = enclosingInstanceIn(g, d) === id ? getComponentSourceId(g.get(d)) : undefined;\n\t\tif (src && !taken.has(src)) {\n\t\t\tmap.set(d, src);\n\t\t\ttaken.add(src);\n\t\t}\n\t}\n\tconst nested: string[] = [];\n\tconst replaced = new Map<string, CanvasNode>();\n\tfor (const d of sub) {\n\t\tlet n = g.get(d)!;\n\t\tconst nid = map.get(d) ?? d;\n\t\tconst parentId = n.parentId !== undefined ? map.get(n.parentId) ?? n.parentId : undefined;\n\t\tn = { ...n, id: nid, ...(parentId !== undefined ? { parentId } : {}) };\n\t\tif (d === id) {\n\t\t\tconst defs: Record<string, CanvasComponentPropertyDef> = {};\n\t\t\tfor (const [k, v] of Object.entries(inst.properties ?? {})) {\n\t\t\t\tif (typeof v === 'boolean') defs[k] = { type: 'boolean', default: v };\n\t\t\t\telse if (typeof v === 'string' && isCanvasComponent(g.get(v))) defs[k] = { type: 'instanceSwap', default: v };\n\t\t\t\telse if (typeof v === 'string') defs[k] = { type: 'text', default: v };\n\t\t\t}\n\t\t\tconst data = { ...dataOf(n) };\n\t\t\tdelete data[INSTANCE_DATA_KEY];\n\t\t\tdelete data[COMPONENT_SOURCE_DATA_KEY];\n\t\t\tdelete data[COMPONENT_OVERRIDES_DATA_KEY];\n\t\t\tdata[COMPONENT_DATA_KEY] = Object.keys(defs).length ? { properties: defs } : {};\n\t\t\tn = withData(n, data);\n\t\t} else if (enclosingInstanceIn(g, d) === id) {\n\t\t\tif (isCanvasInstance(n)) nested.push(nid);\n\t\t\telse n = unlinked(n);\n\t\t}\n\t\treplaced.set(d, n);\n\t}\n\tlet after: CanvasNode[] = nodes.map((n) => replaced.get(n.id) ?? n);\n\tfor (const nn of nested) after = rebaseInstance(after, nn, options.tokens);\n\treturn done(nodes, after, [compId], options);\n}\n\n// ---------------------------------------------------------------------------------------------\n// Editing the variants of a component set\n\nexport interface AddCanvasVariantOptions extends CanvasComponentSyncOptions {\n\t/** Variant to duplicate (default: the set's default variant). */\n\tfrom?: string;\n\t/**\n\t * Property values of the new variant; properties not named keep the source's values. New\n\t * property names are added to every other variant with `defaultValue`. Default: the next\n\t * unused value of the first property (`Variant5`, …), see `getNextVariantValues`.\n\t */\n\tvariant?: Readonly<Record<string, string>>;\n\t/** Position in the set's space (default: right of the source, or by the set's auto layout). */\n\tposition?: XYPosition;\n\t/** Id of the new variant (its layers get `<id>-<sourceLayerId>`). */\n\tid?: string;\n\t/** Value the other variants take for a new property (default `Default`). */\n\tdefaultValue?: string;\n}\n\nexport interface SetCanvasVariantPropertiesOptions extends CanvasComponentSyncOptions {\n\t/** Value the other variants take for a new property (default `Default`). */\n\tdefaultValue?: string;\n}\n\n/** Gap between a new variant and its neighbours, and the set's padding around it. */\nconst VARIANT_GAP = 20;\n\nconst variantKey = (values: Readonly<Record<string, string>>) => formatVariantName(values);\n\nfunction variantNameProblem(kind: 'property' | 'value', s: string): string | undefined {\n\tconst label = kind === 'property' ? 'Variant property names' : 'Variant values';\n\tif (!s) return `${label} must not be empty.`;\n\tif (/[,=]/.test(s)) return `${label} can't contain \",\" or \"=\".`;\n\treturn undefined;\n}\n\n/** Trimmed, validated variant pairs, or the reason they are invalid. */\nfunction cleanVariantPairs(pairs: Readonly<Record<string, unknown>>): Record<string, string> | string {\n\tconst out: Record<string, string> = {};\n\tfor (const [rawKey, rawValue] of Object.entries(pairs)) {\n\t\tif (typeof rawValue !== 'string') return `\"${rawKey}\" takes a text value.`;\n\t\tconst k = rawKey.trim();\n\t\tconst v = rawValue.trim();\n\t\tconst problem = variantNameProblem('property', k) ?? variantNameProblem('value', v);\n\t\tif (problem) return problem;\n\t\tout[k] = v;\n\t}\n\treturn out;\n}\n\n/** Property order of a set (first seen, in layer order) for the given values. */\nfunction variantOrder(ids: readonly string[], values: ReadonlyMap<string, Readonly<Record<string, string>>>): string[] {\n\tconst order: string[] = [];\n\tfor (const id of ids) for (const k of Object.keys(values.get(id) ?? {})) if (!order.includes(k)) order.push(k);\n\treturn order;\n}\n\n/** `node` carrying variant `values` (explicit `data.component.variant`, and its name). */\nfunction withVariantValues<T extends CanvasNode>(node: T, values: Record<string, string>, order: readonly string[]): T {\n\tconst name = formatVariantName(values, order);\n\tconst data: Data = { ...dataOf(node), [COMPONENT_DATA_KEY]: { ...(getComponentData(node) ?? {}), variant: values }, label: name };\n\tif (typeof data.name === 'string' && data.name.includes('=')) data.name = name;\n\treturn same(dataOf(node), data) ? node : withData(node, data);\n}\n\n/**\n * The variant among `list` matching most of `wanted`; ties go to the one matching the earlier\n * properties, then to the first in layer order.\n */\nfunction closestVariant(list: readonly CanvasNode[], wanted: Readonly<Record<string, string>>): string | undefined {\n\tconst entries = Object.entries(wanted);\n\tlet best: { score: number[]; id: string } | undefined;\n\tconst better = (a: number[], b: number[]) => {\n\t\tfor (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return a[i] > b[i];\n\t\treturn false;\n\t};\n\tfor (const v of list) {\n\t\tconst values = getVariantValues(v);\n\t\tconst matches = entries.map(([k, val]) => (values[k] === val ? 1 : 0));\n\t\tconst score = [matches.reduce((a: number, b) => a + b, 0), ...matches];\n\t\tif (!best || better(score, best.score)) best = { score, id: v.id };\n\t}\n\treturn best?.id;\n}\n\ninterface VariantEdit {\n\tset: CanvasNode;\n\tvariants: CanvasNode[];\n\tvalues: Map<string, Record<string, string>>;\n}\n\nfunction variantEditIn(g: Graph, setId: string): VariantEdit | { code: CanvasComponentErrorCode; reason: string } {\n\tconst set = g.get(setId);\n\tif (!isCanvasComponentSet(set)) return { code: 'not-found', reason: 'Choose a component set.' };\n\tif (owningInstanceIn(g, setId)) return { code: 'inside-instance', reason: \"A component set inside an instance can't be edited: edit its main.\" };\n\tconst variants = variantsIn(g, setId);\n\treturn { set: set!, variants, values: new Map(variants.map((v) => [v.id, getVariantValues(v)])) };\n}\n\n/** Adds the properties of `pairs` no variant has yet to every variant, with `defaultValue`. */\nfunction addMissingProperties(edit: VariantEdit, pairs: Readonly<Record<string, string>>, defaultValue: string): string | undefined {\n\tconst known = new Set<string>();\n\tfor (const v of edit.values.values()) for (const k of Object.keys(v)) known.add(k);\n\tconst added = Object.keys(pairs).filter((k) => !known.has(k));\n\tif (added.length === 0) return undefined;\n\tconst problem = variantNameProblem('value', defaultValue);\n\tif (problem) return problem;\n\tconst defs = dataOf(edit.set)[COMPONENT_SET_DATA_KEY]?.properties;\n\tconst clash = isRecord(defs) ? added.find((k) => k in defs) : undefined;\n\tif (clash) return `\"${clash}\" is a component property.`;\n\tfor (const [id, v] of edit.values) {\n\t\tconst next = { ...v };\n\t\tfor (const k of added) next[k] = defaultValue;\n\t\tedit.values.set(id, next);\n\t}\n\treturn undefined;\n}\n\n/** The name of the first of `ids` whose combination another variant shares. */\nfunction duplicateAmong(values: ReadonlyMap<string, Readonly<Record<string, string>>>, ids: Iterable<string>): string | undefined {\n\tfor (const id of ids) {\n\t\tconst key = variantKey(values.get(id)!);\n\t\tfor (const [other, v] of values) {\n\t\t\tif (other !== id && variantKey(v) === key) return formatVariantName(values.get(id)!, variantOrder([...values.keys()], values));\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/** Variant nodes whose values changed in `edit`, by id. */\nfunction writeVariantEdit(edit: VariantEdit, extra: readonly string[] = []): Map<string, CanvasNode> {\n\tconst order = variantOrder([...edit.variants.map((v) => v.id), ...extra], edit.values);\n\tconst updates = new Map<string, CanvasNode>();\n\tfor (const v of edit.variants) {\n\t\tconst next = withVariantValues(v, edit.values.get(v.id)!, order);\n\t\tif (next !== v) updates.set(v.id, next);\n\t}\n\treturn updates;\n}\n\nfunction nextVariantValuesIn(g: Graph, setId: string, fromId: string | undefined): Record<string, string> {\n\tconst variants = variantsIn(g, setId);\n\tconst base = getVariantValues(g.get(fromId));\n\tconst first = variantOptionsIn(g, setId)[0];\n\tconst prop = first?.name ?? 'Property 1';\n\tconst used = new Set(variants.map((v) => variantKey(getVariantValues(v))));\n\tfor (let k = variants.length + 1; ; k++) {\n\t\tconst values = { ...base, [prop]: `Variant${k}` };\n\t\tif (!first?.values.includes(`Variant${k}`) && !used.has(variantKey(values))) return values;\n\t}\n}\n\n/**\n * The values `addCanvasVariant` gives a new variant by default: the source's (default: the\n * set's default variant) with the first property set to the next unused `Variant<n>`.\n */\nexport function getNextVariantValues(nodes: readonly CanvasNode[], setId: string, from?: string): Record<string, string> {\n\tconst g = new Graph(nodes);\n\treturn nextVariantValuesIn(g, setId, from ?? defaultVariantIn(g, setId));\n}\n\nfunction relayoutSet(nodes: CanvasNode[], setId: string, options: CanvasComponentSyncOptions): CanvasNode[] {\n\tif (options.layout === false || !getAutoLayout(nodes.find((n) => n.id === setId))) return nodes;\n\treturn applyNodeUpdates(nodes, layoutUpMany(nodes, [setId], typeof options.layout === 'object' ? options.layout : {}));\n}\n\n/**\n * Adds a variant to a component set by duplicating one (default: the default variant) with new\n * ids and the given property values. New property names are added to every other variant (with\n * `defaultValue`), so the set stays consistent; a combination another variant has is refused.\n * The copy goes right of its source (the set grows to fit) or where the set's auto layout puts it.\n */\nexport function addCanvasVariant<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tsetId: string,\n\toptions: AddCanvasVariantOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst edit = variantEditIn(g, setId);\n\tif (!('set' in edit)) return fail(nodes, edit.code, edit.reason);\n\tif (edit.variants.length === 0) return fail(nodes, 'invalid', 'The set has no variant to duplicate.');\n\tconst fromId = options.from ?? defaultVariantIn(g, setId)!;\n\tconst source = edit.variants.find((v) => v.id === fromId);\n\tif (!source) return fail(nodes, 'not-found', `\"${fromId}\" is not a variant of this set.`);\n\tconst wanted = cleanVariantPairs(options.variant ?? nextVariantValuesIn(g, setId, fromId));\n\tif (typeof wanted === 'string') return fail(nodes, 'invalid', wanted);\n\tconst problem = addMissingProperties(edit, wanted, (options.defaultValue ?? 'Default').trim());\n\tif (problem) return fail(nodes, 'invalid', problem);\n\tconst id = options.id ?? uniqueId(`${setId}-variant`, g.byId);\n\tif (g.get(id)) return fail(nodes, 'invalid', `Id \"${id}\" is taken.`);\n\tedit.values.set(id, { ...edit.values.get(fromId)!, ...wanted });\n\tconst dup = duplicateAmong(edit.values, [id]);\n\tif (dup) return fail(nodes, 'invalid', `A variant with ${dup} exists already.`);\n\n\t// Placement: the given position, the set's auto layout, or the first free spot right of the source.\n\tconst autoLayout = !!getAutoLayout(edit.set);\n\tconst { width, height } = getNodeSize(source);\n\tlet position: XYPosition = options.position ? { ...options.position } : { ...source.position };\n\tif (!options.position && !autoLayout) {\n\t\tconst rects = edit.variants.map((v) => ({ ...v.position, ...getNodeSize(v) }));\n\t\tposition = { x: source.position.x + width + VARIANT_GAP, y: source.position.y };\n\t\tfor (let k = 0; k <= rects.length; k++) {\n\t\t\tconst hit = rects.find((r) => position.x < r.x + r.width && r.x < position.x + width && position.y < r.y + r.height && r.y < position.y + height);\n\t\t\tif (!hit) break;\n\t\t\tposition = { x: hit.x + hit.width + VARIANT_GAP, y: position.y };\n\t\t}\n\t}\n\n\t// Copy the source's subtree with fresh ids, after the set's last layer.\n\tconst taken = new Set(g.byId.keys());\n\ttaken.add(id);\n\tconst map = new Map<string, string>([[fromId, id]]);\n\tconst sub = g.subtree(fromId);\n\tfor (const d of sub) {\n\t\tif (d === fromId) continue;\n\t\tconst nid = uniqueId(`${id}-${d}`, taken);\n\t\ttaken.add(nid);\n\t\tmap.set(d, nid);\n\t}\n\tconst order = variantOrder([...edit.variants.map((v) => v.id), id], edit.values);\n\tconst subSet = new Set(sub);\n\tconst copies: CanvasNode[] = nodes\n\t\t.filter((n) => subSet.has(n.id))\n\t\t.map((n) => {\n\t\t\tconst next = { ...n, id: map.get(n.id)! } as Data;\n\t\t\tif (n.parentId !== undefined && n.id !== fromId) next.parentId = map.get(n.parentId) ?? n.parentId;\n\t\t\tfor (const k of RUNTIME_KEYS) delete next[k];\n\t\t\tif (n.id !== fromId) return next as CanvasNode;\n\t\t\tnext.position = position;\n\t\t\treturn withVariantValues(next as CanvasNode, edit.values.get(id)!, order);\n\t\t});\n\tconst updates = writeVariantEdit(edit, [id]);\n\tconst inSet = new Set(g.subtree(setId));\n\tlet last = -1;\n\tnodes.forEach((n, i) => {\n\t\tif (inSet.has(n.id)) last = i;\n\t});\n\tlet after: CanvasNode[] = [];\n\tnodes.forEach((n, i) => {\n\t\tafter.push(updates.get(n.id) ?? n);\n\t\tif (i === last) after.push(...copies);\n\t});\n\n\tif (autoLayout && !options.position) after = relayoutSet(after, setId, options);\n\telse {\n\t\tconst size = getNodeSize(edit.set);\n\t\tconst w = Math.max(size.width, position.x + width + VARIANT_GAP);\n\t\tconst h = Math.max(size.height, position.y + height + VARIANT_GAP);\n\t\tif (w !== size.width || h !== size.height) after = applyNodeUpdates(after, [{ id: setId, width: w, height: h }]);\n\t}\n\treturn done(nodes, after, [id], options);\n}\n\n/**\n * Removes a variant from its set (refusing the last one). Its instances switch to the closest\n * remaining variant (most matching values), keeping their overrides like a variant switch, and\n * instance swap values and defaults pointing at it point at that variant too.\n */\nexport function removeCanvasVariant<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tvariantId: string,\n\toptions: CanvasComponentSyncOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst set = variantSetIn(g, variantId);\n\tif (!set) return fail(nodes, 'not-found', 'Choose a variant of a component set.');\n\tif (owningInstanceIn(g, set.id)) return fail(nodes, 'inside-instance', \"A component set inside an instance can't be edited: edit its main.\");\n\tconst others = variantsIn(g, set.id).filter((v) => v.id !== variantId);\n\tif (others.length === 0) return fail(nodes, 'invalid', 'A component set needs at least one variant: delete the set instead.');\n\tconst replacement = closestVariant(others, getVariantValues(g.get(variantId)))!;\n\tconst swap = <T>(v: T): T => (v === variantId ? (replacement as T) : v);\n\n\t// 1. Re-point instances (and instance swap values / defaults), synced while the variant exists.\n\tconst repointed = nodes.map((n) => {\n\t\tconst data = dataOf(n);\n\t\tlet next: Data | undefined;\n\t\tconst inst = getInstanceData(n);\n\t\tif (inst) {\n\t\t\tconst moved: CanvasInstanceData = { ...inst, componentId: swap(inst.componentId) };\n\t\t\tif (inst.properties) moved.properties = Object.fromEntries(Object.entries(inst.properties).map(([k, v]) => [k, swap(v)]));\n\t\t\tif (!same(moved, inst)) next = { ...data, [INSTANCE_DATA_KEY]: moved };\n\t\t}\n\t\tfor (const key of [COMPONENT_DATA_KEY, COMPONENT_SET_DATA_KEY]) {\n\t\t\tconst owner = data[key];\n\t\t\tif (!isRecord(owner) || !isRecord(owner.properties)) continue;\n\t\t\tconst defs: Record<string, CanvasComponentPropertyDef> = {};\n\t\t\tfor (const [k, def] of Object.entries(owner.properties as Record<string, CanvasComponentPropertyDef>)) {\n\t\t\t\tdefs[k] =\n\t\t\t\t\tdef.type === 'instanceSwap'\n\t\t\t\t\t\t? { ...def, default: swap(def.default), ...(def.preferred ? { preferred: [...new Set(def.preferred.map(swap))] } : {}) }\n\t\t\t\t\t\t: def;\n\t\t\t}\n\t\t\tif (!same(defs, owner.properties)) next = { ...(next ?? data), [key]: { ...owner, properties: defs } };\n\t\t}\n\t\treturn next ? withData(n, next) : n;\n\t});\n\tconst mid = syncCanvasComponents(nodes, repointed, options);\n\n\t// 2. Delete the variant (the set's auto layout closes the gap).\n\tconst gone = new Set(new Graph(mid).subtree(variantId));\n\tconst after = relayoutSet(\n\t\tmid.filter((n) => !gone.has(n.id)),\n\t\tset.id,\n\t\toptions\n\t);\n\treturn { ok: true, nodes: syncCanvasComponents(mid, after as TNode[], options), ids: [replacement] };\n}\n\n/**\n * Edits a variant's property values (merged into its current ones). New property names are\n * added to every other variant (with `defaultValue`); a combination another variant has is\n * refused. Instances of the variant stay on it and pick up the new values.\n */\nexport function setCanvasVariantProperties<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tvariantId: string,\n\tvalues: Readonly<Record<string, string>>,\n\toptions: SetCanvasVariantPropertiesOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst set = variantSetIn(g, variantId);\n\tif (!set) return fail(nodes, 'not-found', 'Choose a variant of a component set.');\n\tconst edit = variantEditIn(g, set.id);\n\tif (!('set' in edit)) return fail(nodes, edit.code, edit.reason);\n\tconst wanted = cleanVariantPairs(values);\n\tif (typeof wanted === 'string') return fail(nodes, 'invalid', wanted);\n\tconst problem = addMissingProperties(edit, wanted, (options.defaultValue ?? 'Default').trim());\n\tif (problem) return fail(nodes, 'invalid', problem);\n\tedit.values.set(variantId, { ...edit.values.get(variantId)!, ...wanted });\n\tconst dup = duplicateAmong(edit.values, [variantId]);\n\tif (dup) return fail(nodes, 'invalid', `A variant with ${dup} exists already.`);\n\tconst updates = writeVariantEdit(edit);\n\tif (updates.size === 0) return { ok: true, nodes: nodes as TNode[], ids: [variantId] };\n\treturn done(nodes, nodes.map((n) => updates.get(n.id) ?? n), [variantId], options);\n}\n\n/** Rewrites property values (via `fn`) stored on instances of the set's variants. */\nfunction updateInstanceValues(g: Graph, setId: string, updates: Map<string, CanvasNode>, fn: (props: Record<string, Value>) => Record<string, Value>) {\n\tconst ids = new Set(variantsIn(g, setId).map((v) => v.id));\n\tfor (const n of g.nodes) {\n\t\tconst inst = getInstanceData(n);\n\t\tif (!inst?.properties || !ids.has(inst.componentId)) continue;\n\t\tconst props = fn({ ...inst.properties });\n\t\tif (same(props, inst.properties)) continue;\n\t\tconst cur = updates.get(n.id) ?? n;\n\t\tupdates.set(n.id, withData(cur, { ...dataOf(cur), [INSTANCE_DATA_KEY]: { ...inst, properties: props } }));\n\t}\n}\n\n/** Renames a variant property across a set: every variant, and the values instances store. */\nexport function renameCanvasVariantProperty<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tsetId: string,\n\tname: string,\n\tnewName: string,\n\toptions: CanvasComponentSyncOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst edit = variantEditIn(g, setId);\n\tif (!('set' in edit)) return fail(nodes, edit.code, edit.reason);\n\tconst known = variantOptionsIn(g, setId);\n\tif (!known.some((o) => o.name === name)) return fail(nodes, 'unknown-property', `Unknown variant property \"${name}\".`);\n\tconst to = newName.trim();\n\tconst problem = variantNameProblem('property', to);\n\tif (problem) return fail(nodes, 'invalid', problem);\n\tif (to === name) return { ok: true, nodes: nodes as TNode[], ids: [setId] };\n\tif (known.some((o) => o.name === to)) return fail(nodes, 'invalid', `\"${to}\" exists already.`);\n\tif (to in propertyDefsIn(g, edit.variants[0].id)) return fail(nodes, 'invalid', `\"${to}\" is a component property.`);\n\tconst rename = <T>(v: Record<string, T>) => Object.fromEntries(Object.entries(v).map(([k, val]) => [k === name ? to : k, val]));\n\tfor (const [id, v] of edit.values) edit.values.set(id, rename(v));\n\tconst updates = writeVariantEdit(edit);\n\tupdateInstanceValues(g, setId, updates, (props) => (name in props ? rename(props) : props));\n\treturn done(nodes, nodes.map((n) => updates.get(n.id) ?? n), [setId], options);\n}\n\n/** Renames a value of a variant property across a set: every variant using it, and instances. */\nexport function renameCanvasVariantValue<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tsetId: string,\n\tproperty: string,\n\tvalue: string,\n\tnewValue: string,\n\toptions: CanvasComponentSyncOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst edit = variantEditIn(g, setId);\n\tif (!('set' in edit)) return fail(nodes, edit.code, edit.reason);\n\tconst option = variantOptionsIn(g, setId).find((o) => o.name === property);\n\tif (!option) return fail(nodes, 'unknown-property', `Unknown variant property \"${property}\".`);\n\tif (!option.values.includes(value)) return fail(nodes, 'invalid', `\"${property}\" has no value \"${value}\".`);\n\tconst to = newValue.trim();\n\tconst problem = variantNameProblem('value', to);\n\tif (problem) return fail(nodes, 'invalid', problem);\n\tif (to === value) return { ok: true, nodes: nodes as TNode[], ids: [setId] };\n\tconst touched: string[] = [];\n\tfor (const [id, v] of edit.values) {\n\t\tif (v[property] !== value) continue;\n\t\tedit.values.set(id, { ...v, [property]: to });\n\t\ttouched.push(id);\n\t}\n\tconst dup = duplicateAmong(edit.values, touched);\n\tif (dup) return fail(nodes, 'invalid', `A variant with ${dup} exists already.`);\n\tconst updates = writeVariantEdit(edit);\n\tupdateInstanceValues(g, setId, updates, (props) => (props[property] === value ? { ...props, [property]: to } : props));\n\treturn done(nodes, nodes.map((n) => updates.get(n.id) ?? n), [setId], options);\n}\n","// Portions ported from figcraft/crates/engine/src/cmd/component.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport type { ArrangeHistoryTarget } from './arrangeController.js';\nimport {\n\taddCanvasVariant,\n\tbindCanvasComponentProperty,\n\tcombineCanvasComponentsAsVariants,\n\tcreateCanvasComponent,\n\tcreateCanvasInstance,\n\tdetachCanvasInstance,\n\tgetInstanceData,\n\tgetNodeOverrides,\n\tgetOwningInstance,\n\tgetRootInstance,\n\tgetVariantSet,\n\tinspectCanvasInstance,\n\tisCanvasComponent,\n\tisCanvasComponentSet,\n\tisInsideInstance,\n\tlistCanvasComponents,\n\tpushCanvasInstanceOverrides,\n\tremoveCanvasVariant,\n\trenameCanvasVariantProperty,\n\trenameCanvasVariantValue,\n\tresetCanvasInstanceOverrides,\n\trestoreCanvasMainComponent,\n\tsetCanvasComponentProperty,\n\tsetCanvasInstanceProperties,\n\tsetCanvasVariantProperties,\n\tswapCanvasInstance,\n\tsyncAllCanvasInstances,\n\tsyncCanvasComponents,\n\twouldCreateComponentCycle,\n\ttype AddCanvasVariantOptions,\n\ttype CanvasComponentPropertyDef,\n\ttype CanvasComponentPropertyField,\n\ttype CanvasComponentResult,\n\ttype CanvasComponentSyncOptions,\n\ttype CreateCanvasComponentOptions,\n\ttype CreateCanvasInstanceOptions\n} from './components.js';\nimport type { CanvasNode } from './types.js';\n\nexport interface CanvasComponentsControllerOptions<TNode extends CanvasNode = CanvasNode> extends CanvasComponentSyncOptions {\n\tgetNodes: () => TNode[];\n\tsetNodes: (nodes: TNode[]) => void;\n\t/** When set, every component action is one undoable step. */\n\thistory?: ArrangeHistoryTarget;\n\t/** The selection (default: nodes with `selected`). */\n\tgetSelectedIds?: () => string[];\n\t/** Called by `goToMainComponent` with the main (e.g. to pan the viewport to it). */\n\tonReveal?: (node: TNode) => void;\n}\n\n/** One side of an undo step: nodes to put (or remove, `null`) and the array order to restore. */\ninterface NodePatch {\n\tchanges: Map<string, CanvasNode | null>;\n\torder: string[];\n}\n\nfunction makePatch(from: readonly CanvasNode[], to: readonly CanvasNode[]): NodePatch {\n\tconst fromIds = new Map(from.map((n) => [n.id, n]));\n\tconst toIds = new Set(to.map((n) => n.id));\n\tconst changes = new Map<string, CanvasNode | null>();\n\tfor (const n of to) if (fromIds.get(n.id) !== n) changes.set(n.id, n);\n\tfor (const n of from) if (!toIds.has(n.id)) changes.set(n.id, null);\n\treturn { changes, order: to.map((n) => n.id) };\n}\n\n/** Applies a patch to the current nodes, keeping nodes it doesn't name as they are now. */\nfunction applyPatch<TNode extends CanvasNode>(current: readonly TNode[], patch: NodePatch): TNode[] {\n\tconst byId = new Map<string, CanvasNode>(current.map((n) => [n.id, n]));\n\tfor (const [id, n] of patch.changes) {\n\t\tif (n) byId.set(id, n);\n\t\telse byId.delete(id);\n\t}\n\tconst out: CanvasNode[] = [];\n\tconst placed = new Set<string>();\n\tfor (const id of patch.order) {\n\t\tconst n = byId.get(id);\n\t\tif (n && !placed.has(id)) {\n\t\t\tout.push(n);\n\t\t\tplaced.add(id);\n\t\t}\n\t}\n\tfor (const n of current) if (!placed.has(n.id) && byId.has(n.id)) out.push(byId.get(n.id)!);\n\treturn out as TNode[];\n}\n\n/**\n * Framework-agnostic components controller shared by the React hook and the Svelte rune. Its\n * actions (create component / instance, combine as variants, add / remove / edit / rename\n * variants, detach, reset, push, swap, set properties, restore main) are one undoable step each with a `history`; `sync()` keeps\n * instances following their mains after any other edit (call it after the nodes change).\n * Each `can…` check returns `true` or the reason the action can't run.\n */\nexport function createCanvasComponentsController<TNode extends CanvasNode = CanvasNode>(options: CanvasComponentsControllerOptions<TNode>) {\n\tlet synced: TNode[] | undefined;\n\tconst syncOptions = (): CanvasComponentSyncOptions => ({\n\t\tidGenerator: options.idGenerator,\n\t\ttextTypes: options.textTypes,\n\t\tlayout: options.layout,\n\t\ttokens: options.tokens\n\t});\n\tconst selection = (ids?: Iterable<string>): string[] =>\n\t\tids ? [...ids] : options.getSelectedIds?.() ?? options.getNodes().filter((n) => n.selected).map((n) => n.id);\n\tconst target = (id?: string) => id ?? selection()[0];\n\n\tfunction set(next: TNode[]) {\n\t\tsynced = next;\n\t\toptions.setNodes(next);\n\t}\n\n\t/** Syncs edits made since the last sync (overrides, main edits) and returns the nodes. */\n\tfunction sync(): TNode[] {\n\t\tconst nodes = options.getNodes();\n\t\tif (synced === undefined || nodes === synced) {\n\t\t\tsynced = nodes;\n\t\t\treturn nodes;\n\t\t}\n\t\tconst next = syncCanvasComponents(synced, nodes, syncOptions());\n\t\tsynced = next;\n\t\tif (next !== nodes) options.setNodes(next);\n\t\treturn next;\n\t}\n\n\tfunction run(name: string, op: (nodes: TNode[]) => CanvasComponentResult<TNode>): CanvasComponentResult<TNode> {\n\t\tconst before = sync();\n\t\tconst result = op(before);\n\t\tif (!result.ok || result.nodes === before) return result;\n\t\tconst forward = makePatch(before, result.nodes);\n\t\tconst backward = makePatch(result.nodes, before);\n\t\tconst execute = () => set(applyPatch(options.getNodes(), forward));\n\t\tconst undo = () => set(applyPatch(options.getNodes(), backward));\n\t\tif (options.history) options.history.execute({ name, execute, undo });\n\t\telse execute();\n\t\treturn result;\n\t}\n\n\tconst reason = (result: CanvasComponentResult<TNode>): true | string => (result.ok ? true : result.reason);\n\tconst nodes = () => options.getNodes();\n\tconst refused = (why: string) => ({ ok: false, nodes: nodes(), code: 'not-found', reason: why }) as CanvasComponentResult<TNode>;\n\t/** The set `id` names (a set, or a variant's set; default: the selection's). */\n\tconst setOf = (id?: string): string | undefined => {\n\t\tconst at = target(id);\n\t\tconst node = at ? nodes().find((n) => n.id === at) : undefined;\n\t\tif (!node) return undefined;\n\t\treturn isCanvasComponentSet(node) ? node.id : getVariantSet(nodes(), node.id);\n\t};\n\t/** The variant `id` names (default: the first selected variant). */\n\tconst variantOf = (id?: string): string | undefined => {\n\t\tconst at = target(id);\n\t\treturn at && getVariantSet(nodes(), at) ? at : undefined;\n\t};\n\tconst NO_SET = 'Select a component set or one of its variants.';\n\tconst NO_VARIANT = 'Select a variant of a component set.';\n\ttype VariantOptions = Omit<AddCanvasVariantOptions, keyof CanvasComponentSyncOptions>;\n\t/** `addVariant` options with `from` defaulting to the selected variant. */\n\tconst addOptions = (setId: string | undefined, addOptions_: VariantOptions): VariantOptions => {\n\t\tif (addOptions_.from !== undefined || setId !== undefined) return addOptions_;\n\t\tconst v = variantOf();\n\t\treturn v ? { ...addOptions_, from: v } : addOptions_;\n\t};\n\n\tconst controller = {\n\t\tsync,\n\t\t/** Rebuilds every instance from its main (e.g. after loading), not recorded in history. */\n\t\tsyncAll(): TNode[] {\n\t\t\tconst next = syncAllCanvasInstances(nodes(), syncOptions());\n\t\t\tif (next !== nodes()) set(next);\n\t\t\telse synced = next;\n\t\t\treturn next;\n\t\t},\n\n\t\t/** Create Component from `ids` (default: the selection). */\n\t\tcreateComponent(ids?: Iterable<string>, createOptions: Omit<CreateCanvasComponentOptions, keyof CanvasComponentSyncOptions> = {}) {\n\t\t\tconst list = selection(ids);\n\t\t\treturn run('Create Component', (n) => createCanvasComponent(n, list, { ...syncOptions(), ...createOptions }));\n\t\t},\n\t\tcanCreateComponent: (ids?: Iterable<string>) => reason(createCanvasComponent(nodes(), selection(ids), { layout: false })),\n\n\t\t/** Combine as Variants: wraps the selected components in a component set. */\n\t\tcombineAsVariants(ids?: Iterable<string>, combineOptions: { id?: string; name?: string; padding?: number } = {}) {\n\t\t\tconst list = selection(ids);\n\t\t\treturn run('Combine as Variants', (n) => combineCanvasComponentsAsVariants(n, list, { ...syncOptions(), ...combineOptions }));\n\t\t},\n\t\tcanCombineAsVariants: (ids?: Iterable<string>) => reason(combineCanvasComponentsAsVariants(nodes(), selection(ids), { layout: false })),\n\n\t\t/** Create Instance of a component or component set. */\n\t\tcreateInstance(componentId: string, createOptions: Omit<CreateCanvasInstanceOptions, keyof CanvasComponentSyncOptions> = {}) {\n\t\t\treturn run('Create Instance', (n) => createCanvasInstance(n, componentId, { ...syncOptions(), ...createOptions }));\n\t\t},\n\t\tcanCreateInstance(componentId?: string, parentId?: string): true | string {\n\t\t\tconst list = nodes();\n\t\t\tconst target = componentId ? list.find((n) => n.id === componentId) : undefined;\n\t\t\tif (!target || (!isCanvasComponent(target) && !isCanvasComponentSet(target))) return 'Choose a component or component set.';\n\t\t\tif (wouldCreateComponentCycle(list, componentId!, parentId)) return \"An instance can't be placed inside its own main component.\";\n\t\t\tif (parentId && getOwningInstance(list, parentId)) return \"Instances can't gain layers: place it outside the instance.\";\n\t\t\treturn true;\n\t\t},\n\n\t\t/** Detach Instance (default: the selected instances). */\n\t\tdetachInstance(ids?: Iterable<string>) {\n\t\t\tconst list = selection(ids);\n\t\t\treturn run('Detach Instance', (n) => detachCanvasInstance(n, list, syncOptions()));\n\t\t},\n\t\tcanDetachInstance(ids?: Iterable<string>): true | string {\n\t\t\tconst list = nodes();\n\t\t\tconst instances = selection(ids).filter((id) => getInstanceData(list.find((n) => n.id === id)));\n\t\t\tif (instances.length === 0) return 'Select an instance.';\n\t\t\tif (instances.some((id) => isInsideInstance(list, id))) return \"A nested instance can't be detached on its own: detach the outer instance first.\";\n\t\t\treturn true;\n\t\t},\n\n\t\t/** Reset overrides of an instance or layer (default: the first selected); `group` resets one. */\n\t\tresetOverrides(id?: string, group?: string) {\n\t\t\tconst at = target(id);\n\t\t\tif (!at) return { ok: false, nodes: nodes(), code: 'not-found', reason: 'Select an instance or a layer inside one.' } as CanvasComponentResult<TNode>;\n\t\t\treturn run(group ? 'Reset Override' : 'Reset All Changes', (n) => resetCanvasInstanceOverrides(n, at, group, syncOptions()));\n\t\t},\n\t\tcanResetOverrides(id?: string): true | string {\n\t\t\tconst at = target(id);\n\t\t\tconst info = at ? inspectCanvasInstance(nodes(), at) : undefined;\n\t\t\tif (!info) return 'Select an instance or a layer inside one.';\n\t\t\tconst changedProps = !info.nested && info.properties.some((p) => p.value !== p.default);\n\t\t\tconst own = info.id === at ? info.overrides.length > 0 : getNodeOverrides(nodes().find((n) => n.id === at)).length > 0;\n\t\t\treturn own || (info.id === at && changedProps) ? true : 'No overrides to reset.';\n\t\t},\n\n\t\t/** Push the (outermost) instance's overrides to its main component. */\n\t\tpushOverrides(id?: string) {\n\t\t\tconst at = target(id);\n\t\t\tif (!at) return { ok: false, nodes: nodes(), code: 'not-found', reason: 'Select an instance.' } as CanvasComponentResult<TNode>;\n\t\t\treturn run('Push Changes to Main Component', (n) => pushCanvasInstanceOverrides(n, at, syncOptions()));\n\t\t},\n\t\tcanPushOverrides(id?: string): true | string {\n\t\t\tconst at = target(id);\n\t\t\treturn at ? reason(pushCanvasInstanceOverrides(nodes(), at, { layout: false })) : 'Select an instance.';\n\t\t},\n\n\t\t/** Swap the selected instances to another component (or a set's default variant). */\n\t\tswapInstance(componentId: string, ids?: Iterable<string>, swapOptions: { keepTextOverridesOnly?: boolean } = {}) {\n\t\t\tconst list = selection(ids);\n\t\t\treturn run('Swap Instance', (n) => swapCanvasInstance(n, list, componentId, { ...syncOptions(), ...swapOptions }));\n\t\t},\n\t\tcanSwapInstance(componentId?: string, ids?: Iterable<string>): true | string {\n\t\t\tconst list = nodes();\n\t\t\tconst instances = selection(ids).filter((id) => getInstanceData(list.find((n) => n.id === id)));\n\t\t\tif (instances.length === 0) return 'Select an instance.';\n\t\t\tif (componentId === undefined) return true;\n\t\t\tconst main = list.find((n) => n.id === componentId);\n\t\t\tif (!main || (!isCanvasComponent(main) && !isCanvasComponentSet(main))) return 'Choose a component or component set.';\n\t\t\tfor (const id of instances) {\n\t\t\t\tconst node = list.find((n) => n.id === id)!;\n\t\t\t\tif (wouldCreateComponentCycle(list, componentId, node.parentId)) return \"An instance can't show the component it is inside.\";\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\n\t\t/** Set variant / component property values on the selected instances (one step). */\n\t\tsetInstanceProperties(properties: Record<string, unknown>, ids?: Iterable<string>) {\n\t\t\tconst list = selection(ids);\n\t\t\treturn run('Set Instance Properties', (n) => setCanvasInstanceProperties(n, list, properties, syncOptions()));\n\t\t},\n\t\tcanSetInstanceProperties(ids?: Iterable<string>): true | string {\n\t\t\tconst list = nodes();\n\t\t\tconst sel = selection(ids);\n\t\t\treturn sel.length > 0 && sel.every((id) => getInstanceData(list.find((n) => n.id === id))) ? true : 'Select instances.';\n\t\t},\n\n\t\t/** Restore Main Component from an instance whose main was deleted. */\n\t\trestoreMainComponent(id?: string) {\n\t\t\tconst at = target(id);\n\t\t\tif (!at) return { ok: false, nodes: nodes(), code: 'not-found', reason: 'Select an instance.' } as CanvasComponentResult<TNode>;\n\t\t\treturn run('Restore Main Component', (n) => restoreCanvasMainComponent(n, at, syncOptions()));\n\t\t},\n\t\tcanRestoreMainComponent(id?: string): true | string {\n\t\t\tconst at = target(id);\n\t\t\treturn at ? reason(restoreCanvasMainComponent(nodes(), at, { layout: false })) : 'Select an instance.';\n\t\t},\n\n\t\t/** Adds, edits or (with `null`) deletes a component property. */\n\t\tsetComponentProperty(componentId: string, name: string, def: CanvasComponentPropertyDef | null, propertyOptions: { newName?: string } = {}) {\n\t\t\treturn run(def ? 'Edit Component Property' : 'Delete Component Property', (n) =>\n\t\t\t\tsetCanvasComponentProperty(n, componentId, name, def, { ...syncOptions(), ...propertyOptions })\n\t\t\t);\n\t\t},\n\n\t\t/**\n\t\t * Adds a variant to a set (default: the selection's set) by duplicating `from` (default:\n\t\t * the selected variant, else the default variant) with the given `variant` values.\n\t\t */\n\t\taddVariant(setId?: string, variantOptions: VariantOptions = {}) {\n\t\t\tconst set = setOf(setId);\n\t\t\tif (!set) return refused(NO_SET);\n\t\t\tconst opts = addOptions(setId, variantOptions);\n\t\t\treturn run('Add Variant', (n) => addCanvasVariant(n, set, { ...syncOptions(), ...opts }));\n\t\t},\n\t\tcanAddVariant(setId?: string, variantOptions: VariantOptions = {}): true | string {\n\t\t\tconst set = setOf(setId);\n\t\t\treturn set ? reason(addCanvasVariant(nodes(), set, { ...addOptions(setId, variantOptions), layout: false })) : NO_SET;\n\t\t},\n\n\t\t/** Removes a variant (default: the selected one); its instances switch to the closest variant. */\n\t\tremoveVariant(variantId?: string) {\n\t\t\tconst v = variantOf(variantId);\n\t\t\tif (!v) return refused(NO_VARIANT);\n\t\t\treturn run('Remove Variant', (n) => removeCanvasVariant(n, v, syncOptions()));\n\t\t},\n\t\tcanRemoveVariant(variantId?: string): true | string {\n\t\t\tconst v = variantOf(variantId);\n\t\t\treturn v ? reason(removeCanvasVariant(nodes(), v, { layout: false })) : NO_VARIANT;\n\t\t},\n\n\t\t/** Edits a variant's property values (default: the selected variant). */\n\t\tsetVariantProperties(values: Record<string, string>, variantId?: string, variantOptions: { defaultValue?: string } = {}) {\n\t\t\tconst v = variantOf(variantId);\n\t\t\tif (!v) return refused(NO_VARIANT);\n\t\t\treturn run('Edit Variant', (n) => setCanvasVariantProperties(n, v, values, { ...syncOptions(), ...variantOptions }));\n\t\t},\n\t\tcanSetVariantProperties(values?: Record<string, string>, variantId?: string): true | string {\n\t\t\tconst v = variantOf(variantId);\n\t\t\tif (!v) return NO_VARIANT;\n\t\t\treturn values ? reason(setCanvasVariantProperties(nodes(), v, values, { layout: false })) : true;\n\t\t},\n\n\t\t/** Renames a variant property across a set (default: the selection's set). */\n\t\trenameVariantProperty(name: string, newName: string, setId?: string) {\n\t\t\tconst set = setOf(setId);\n\t\t\tif (!set) return refused(NO_SET);\n\t\t\treturn run('Rename Variant Property', (n) => renameCanvasVariantProperty(n, set, name, newName, syncOptions()));\n\t\t},\n\t\tcanRenameVariantProperty(name?: string, newName?: string, setId?: string): true | string {\n\t\t\tconst set = setOf(setId);\n\t\t\tif (!set) return NO_SET;\n\t\t\treturn name !== undefined && newName !== undefined ? reason(renameCanvasVariantProperty(nodes(), set, name, newName, { layout: false })) : true;\n\t\t},\n\n\t\t/** Renames a value of a variant property across a set (default: the selection's set). */\n\t\trenameVariantValue(property: string, value: string, newValue: string, setId?: string) {\n\t\t\tconst set = setOf(setId);\n\t\t\tif (!set) return refused(NO_SET);\n\t\t\treturn run('Rename Variant Value', (n) => renameCanvasVariantValue(n, set, property, value, newValue, syncOptions()));\n\t\t},\n\t\tcanRenameVariantValue(property?: string, value?: string, newValue?: string, setId?: string): true | string {\n\t\t\tconst set = setOf(setId);\n\t\t\tif (!set) return NO_SET;\n\t\t\treturn property !== undefined && value !== undefined && newValue !== undefined\n\t\t\t\t? reason(renameCanvasVariantValue(nodes(), set, property, value, newValue, { layout: false }))\n\t\t\t\t: true;\n\t\t},\n\n\t\t/** Binds (or with `null` unbinds) a main layer's field to a component property. */\n\t\tbindComponentProperty(layerId: string, field: CanvasComponentPropertyField, name: string | null) {\n\t\t\treturn run('Bind Component Property', (n) => bindCanvasComponentProperty(n, layerId, field, name, syncOptions()));\n\t\t},\n\n\t\t/** Selects the main component of an instance (or of the instance a layer is in). */\n\t\tgoToMainComponent(id?: string): string | undefined {\n\t\t\tconst at = target(id);\n\t\t\tconst list = sync();\n\t\t\tconst inst = at ? getOwningInstance(list, at) : undefined;\n\t\t\tconst main = inst ? getInstanceData(list.find((n) => n.id === inst))?.componentId : undefined;\n\t\t\tconst node = main ? list.find((n) => n.id === main) : undefined;\n\t\t\tif (!node) return undefined;\n\t\t\tconst next = list.map((n) => (!!n.selected === (n.id === main) ? n : ({ ...n, selected: n.id === main } as TNode)));\n\t\t\tset(next);\n\t\t\toptions.onReveal?.(next.find((n) => n.id === main)!);\n\t\t\treturn main;\n\t\t},\n\t\tcanGoToMainComponent(id?: string): true | string {\n\t\t\tconst at = target(id);\n\t\t\tconst list = nodes();\n\t\t\tconst inst = at ? getOwningInstance(list, at) : undefined;\n\t\t\tif (!inst) return 'Select an instance.';\n\t\t\tconst main = getInstanceData(list.find((n) => n.id === inst))!.componentId;\n\t\t\treturn list.some((n) => n.id === main) ? true : 'The main component was deleted.';\n\t\t},\n\n\t\t/** Components and component sets, for a components panel. */\n\t\tlist: () => listCanvasComponents(nodes()),\n\t\t/** The instance inspector's view of an instance or a layer inside one (default: the selection). */\n\t\tinspect: (id?: string) => {\n\t\t\tconst at = target(id);\n\t\t\treturn at ? inspectCanvasInstance(nodes(), at) : undefined;\n\t\t},\n\t\t/** The outermost instance containing `id`. */\n\t\trootInstanceOf: (id: string) => getRootInstance(nodes(), id)\n\t};\n\treturn controller;\n}\n\nexport type CanvasComponentsController<TNode extends CanvasNode = CanvasNode> = ReturnType<typeof createCanvasComponentsController<TNode>>;\n\n/** Drag data type for dragging a component from a components panel onto a canvas. */\nexport const CANVAS_COMPONENT_DRAG_TYPE = 'application/x-pixerate-component';\n\n/** Reads the component id a components panel put on a drag (or `undefined`). */\nexport function getCanvasComponentDragId(dataTransfer: Pick<DataTransfer, 'getData'> | null | undefined): string | undefined {\n\tconst id = dataTransfer?.getData(CANVAS_COMPONENT_DRAG_TYPE);\n\treturn id ? id : undefined;\n}\n\n","/**\n * Freehand drawing utilities: stroke smoothing, cubic Bézier conversion,\n * ShapeNode generation, and spatial eraser hit-testing within the xyflow framework.\n */\nimport type { CanvasNode, Point, Rect } from './types.js';\nimport { fitFreehandPath, type FitPathDataOptions } from './curveFit.js';\nimport { distanceToBezierPath } from './pathops/bezier.js';\nimport { getNodeShape } from './shapeNodes.js';\nimport { getNodeRect } from './arrange.js';\n\nexport type FreehandTool = 'select' | 'pen' | 'highlighter' | 'eraser';\n\nimport type { CanvasGradientDef } from './gradient.js';\nimport { profileAlongPolyline } from './brushstroke.js';\nimport type { CanvasBrush, CanvasBrushPaint, CanvasBrushstroke } from './brushTypes.js';\n\nexport interface FreehandStrokeStyle {\n\t/** Stroke color (any CSS color string) or gradient definition. */\n\tcolor: string | CanvasGradientDef;\n\t/** Stroke width in flow canvas units. */\n\twidth: number;\n\t/** Stroke opacity (0 to 1). */\n\topacity: number;\n\t/** SVG stroke-linecap: 'round', 'butt', or 'square'. */\n\tcap?: 'round' | 'butt' | 'square';\n\t/** SVG stroke-linejoin: 'round', 'bevel', or 'miter'. */\n\tjoin?: 'round' | 'bevel' | 'miter';\n\t/** Optional gradient definition for the stroke. */\n\tgradient?: CanvasGradientDef;\n\t/**\n\t * Paint the stroke with a dry paint brush (a preset id such as `ink-pen`, or tip settings)\n\t * instead of an SVG stroke; `width` is the brush size. Without it strokes are plain vector\n\t * paths, exactly as before.\n\t */\n\tbrush?: CanvasBrush;\n\t/**\n\t * Gradient paint (or one colour) for a brushed stroke instead of `color`, sampled dab by dab\n\t * along the path; `color` still gives the alpha and fills `paint` stops. Only with `brush`.\n\t */\n\tpaint?: CanvasBrushPaint;\n}\n\n/** Pen data for one freehand point (Pointer Events: pressure 0..1, tilt in degrees). */\nexport interface FreehandPenSample {\n\tpressure?: number;\n\ttiltX?: number;\n\ttiltY?: number;\n}\n\nexport interface FreehandDrawOptions {\n\t/** Cubic curve fitting tolerance (default: 1.0). Lower is tighter to points. */\n\ttolerance?: number;\n\t/** Corner angle threshold in degrees for sharp corners (default: 40). */\n\tcornerAngleDeg?: number;\n\t/** Real-time point streamlining / smoothing factor in [0, 1] (default: 0.35). 0 disables smoothing. */\n\tstreamline?: number;\n}\n\n/** The Pointer Event fields `pointerPenSample` reads (structural; no DOM types). */\nexport interface FreehandPointerLike {\n\tpointerType?: string;\n\tpressure?: number;\n\ttiltX?: number;\n\ttiltY?: number;\n}\n\n/**\n * Pen data of a pointer event for `startStroke` / `continueStroke`: pressure and tilt from pens,\n * nothing from mice and touch (a mouse reports 0.5 while pressed, which is not pressure). A pen\n * reporting 0 pressure (some do on the first event) counts as full.\n */\nexport function pointerPenSample(e: FreehandPointerLike): FreehandPenSample | undefined {\n\tif (e.pointerType !== 'pen') return undefined;\n\tconst out: FreehandPenSample = {};\n\tif (typeof e.pressure === 'number' && e.pressure > 0) out.pressure = Math.min(1, e.pressure);\n\tif (typeof e.tiltX === 'number' && e.tiltX !== 0) out.tiltX = e.tiltX;\n\tif (typeof e.tiltY === 'number' && e.tiltY !== 0) out.tiltY = e.tiltY;\n\treturn out;\n}\n\nexport interface FreehandStroke {\n\tid: string;\n\ttool: FreehandTool;\n\tpoints: Point[];\n\t/** Pen data per point (same length as `points`), when the input reported any. Used by brushed strokes. */\n\tsamples?: FreehandPenSample[];\n\tstyle: FreehandStrokeStyle;\n\tstartTime?: number;\n}\n\n/**\n * Returns default stroke styles tailored for specific drawing tools.\n */\nexport function createDefaultStrokeStyle(tool: FreehandTool): FreehandStrokeStyle {\n\tif (tool === 'highlighter') {\n\t\treturn {\n\t\t\tcolor: '#facc15',\n\t\t\twidth: 24,\n\t\t\topacity: 0.35,\n\t\t\tcap: 'round',\n\t\t\tjoin: 'round'\n\t\t};\n\t}\n\treturn {\n\t\tcolor: '#3b82f6',\n\t\twidth: 3,\n\t\topacity: 1.0,\n\t\tcap: 'round',\n\t\tjoin: 'round'\n\t};\n}\n\n/**\n * Smooths jittery pointer samples using an exponential moving average.\n * The first and last points are anchored to guarantee stroke start and end precision.\n */\nexport function streamlinePoints(points: readonly Point[], factor = 0.35): Point[] {\n\tif (points.length < 3 || factor <= 0) return [...points];\n\tconst clampedFactor = Math.min(Math.max(factor, 0), 0.95);\n\tconst smoothed: Point[] = [points[0]];\n\n\tfor (let i = 1; i < points.length - 1; i++) {\n\t\tconst prev = smoothed[i - 1];\n\t\tconst curr = points[i];\n\t\tconst x = prev[0] * clampedFactor + curr[0] * (1 - clampedFactor);\n\t\tconst y = prev[1] * clampedFactor + curr[1] * (1 - clampedFactor);\n\t\tsmoothed.push([x, y]);\n\t}\n\n\tsmoothed.push(points[points.length - 1]);\n\treturn smoothed;\n}\n\n/**\n * Computes the tight axis-aligned bounding box of a sequence of points,\n * optionally expanded by a margin (such as strokeWidth / 2).\n */\nexport function strokeBoundingBox(points: readonly Point[], margin = 0): Rect {\n\tif (points.length === 0) {\n\t\treturn { x: 0, y: 0, width: 0, height: 0 };\n\t}\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\n\tfor (const [x, y] of points) {\n\t\tif (x < minX) minX = x;\n\t\tif (y < minY) minY = y;\n\t\tif (x > maxX) maxX = x;\n\t\tif (y > maxY) maxY = y;\n\t}\n\n\tconst w = Math.max(maxX - minX, 0);\n\tconst h = Math.max(maxY - minY, 0);\n\n\treturn {\n\t\tx: minX - margin,\n\t\ty: minY - margin,\n\t\twidth: Math.max(w + margin * 2, 1),\n\t\theight: Math.max(h + margin * 2, 1)\n\t};\n}\n\n/**\n * Converts a sequence of points into an SVG path `d` string.\n * Gracefully handles single points (dots) and two-point line segments.\n */\nexport function strokePointsToPathData(\n\tpoints: readonly Point[],\n\toptions: Omit<FitPathDataOptions, 'closed'> = {}\n): string {\n\tif (points.length === 0) return '';\n\tif (points.length === 1) {\n\t\tconst [x, y] = points[0];\n\t\t// Small delta with round cap renders as a circular dot\n\t\treturn `M ${x} ${y} L ${x + 0.01} ${y}`;\n\t}\n\tif (points.length === 2) {\n\t\tconst [p0, p1] = points;\n\t\treturn `M ${p0[0]} ${p0[1]} L ${p1[0]} ${p1[1]}`;\n\t}\n\tconst fitted = fitFreehandPath(points, options);\n\treturn fitted.length > 0 ? fitted : `M ${points[0][0]} ${points[0][1]}`;\n}\n\nexport interface StrokeToShapeNodeOptions {\n\tid?: string;\n\ttolerance?: number;\n\tcornerAngleDeg?: number;\n\t/** Values per pen profile of a brushed stroke (default 32). */\n\tprofileLength?: number;\n}\n\n/**\n * The brush of a brushed stroke node: the style's brush plus pressure and tilt profiles along the\n * points, from the stroke's pen samples. Profiles that carry nothing (no pressure reported, an\n * upright pen) are left out.\n */\nexport function freehandBrushstroke(\n\tbrush: CanvasBrush,\n\tpoints: readonly Point[],\n\tsamples: readonly FreehandPenSample[] | undefined,\n\tprofileLength = 32\n): CanvasBrushstroke {\n\tconst out: CanvasBrushstroke = { brush };\n\tif (!samples || samples.length !== points.length) return out;\n\tconst finite = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v);\n\tif (samples.some((s) => finite(s.pressure))) {\n\t\tconst pressure = profileAlongPolyline(points, samples.map((s) => (finite(s.pressure) ? Math.min(1, Math.max(0, s.pressure)) : 1)), profileLength);\n\t\tif (pressure.some((p) => p !== 1)) out.pressure = pressure;\n\t}\n\tfor (const key of ['tiltX', 'tiltY'] as const) {\n\t\tif (!samples.some((s) => finite(s[key]) && s[key] !== 0)) continue;\n\t\tout[key] = profileAlongPolyline(points, samples.map((s) => (finite(s[key]) ? Math.min(90, Math.max(-90, s[key])) : 0)), profileLength);\n\t}\n\treturn out;\n}\n\n/**\n * Converts a completed freehand stroke into a native xyflow `ShapeNode`.\n * The geometry is normalized to node-local space with `viewBox`, ensuring crisp\n * scaling, non-scaling strokes, and full compatibility with existing shape ops.\n */\nexport function strokeToShapeNode(\n\tstroke: FreehandStroke,\n\toptions: StrokeToShapeNodeOptions = {}\n): CanvasNode {\n\tconst points = stroke.points;\n\tconst strokeWidth = stroke.style.width || 3;\n\tconst bounds = strokeBoundingBox(points, 0);\n\n\t// Normalize points to node-local origin (0, 0)\n\tconst localPoints = points.map(\n\t\t([x, y]) => [x - bounds.x, y - bounds.y] as Point\n\t);\n\n\tconst d = strokePointsToPathData(localPoints, {\n\t\ttolerance: options.tolerance ?? 1.0,\n\t\tcornerAngleDeg: options.cornerAngleDeg ?? 40\n\t});\n\n\tconst width = Math.max(bounds.width, 1);\n\tconst height = Math.max(bounds.height, 1);\n\n\tconst strokePaint = stroke.style.gradient ?? stroke.style.color;\n\tconst strokeGradient =\n\t\tstroke.style.gradient ?? (typeof stroke.style.color === 'object' && stroke.style.color ? stroke.style.color : undefined);\n\n\treturn {\n\t\tid: options.id ?? stroke.id,\n\t\ttype: 'shape',\n\t\tposition: { x: bounds.x, y: bounds.y },\n\t\twidth,\n\t\theight,\n\t\tstyle: {\n\t\t\twidth,\n\t\t\theight\n\t\t},\n\t\tdata: {\n\t\t\tshape: {\n\t\t\t\tkind: 'path',\n\t\t\t\td,\n\t\t\t\tviewBox: [0, 0, width, height],\n\t\t\t\tstroke: strokePaint,\n\t\t\t\tstrokeWidth,\n\t\t\t\topacity: stroke.style.opacity,\n\t\t\t\tstrokeLinecap: stroke.style.cap ?? 'round',\n\t\t\t\tstrokeLinejoin: stroke.style.join ?? 'round',\n\t\t\t\t...(strokeGradient ? { strokeGradient } : {}),\n\t\t\t\t...(stroke.style.brush !== undefined\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tbrush: {\n\t\t\t\t\t\t\t\t...freehandBrushstroke(stroke.style.brush, localPoints, stroke.samples, options.profileLength),\n\t\t\t\t\t\t\t\t...(stroke.style.paint !== undefined ? { paint: stroke.style.paint } : {})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t\tfill: 'none'\n\t\t\t},\n\t\t\ttool: stroke.tool\n\t\t}\n\t};\n}\n\n/**\n * Finds all stroke shape nodes intersecting a given point within a hit radius.\n * Ideal for object eraser interactions.\n */\nexport function findStrokesAtPoint(\n\tpoint: Point,\n\tnodes: readonly CanvasNode[],\n\tradius = 12\n): CanvasNode[] {\n\tconst matches: CanvasNode[] = [];\n\n\tfor (const node of nodes) {\n\t\tconst shape = getNodeShape(node);\n\t\tif (!shape || shape.kind !== 'path') continue;\n\n\t\tconst rect = getNodeRect(node);\n\t\tconst strokeWidth = shape.strokeWidth ?? 2;\n\t\tconst effectiveRadius = radius + strokeWidth / 2;\n\n\t\t// Fast AABB rejection test\n\t\tif (\n\t\t\tpoint[0] < rect.x - effectiveRadius ||\n\t\t\tpoint[0] > rect.x + rect.width + effectiveRadius ||\n\t\t\tpoint[1] < rect.y - effectiveRadius ||\n\t\t\tpoint[1] > rect.y + rect.height + effectiveRadius\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Local coordinates relative to node origin\n\t\tconst localPoint: Point = [point[0] - rect.x, point[1] - rect.y];\n\n\t\t// Exact distance check to path contours\n\t\ttry {\n\t\t\tconst dist = distanceToBezierPath(shape.d, localPoint);\n\t\t\tif (dist <= effectiveRadius) {\n\t\t\t\tmatches.push(node);\n\t\t\t}\n\t\t} catch {\n\t\t\t// If path is malformed, fall back to AABB inside check\n\t\t\tif (\n\t\t\t\tpoint[0] >= rect.x &&\n\t\t\t\tpoint[0] <= rect.x + rect.width &&\n\t\t\t\tpoint[1] >= rect.y &&\n\t\t\t\tpoint[1] <= rect.y + rect.height\n\t\t\t) {\n\t\t\t\tmatches.push(node);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn matches;\n}\n","/**\n * Headless controller managing freehand drawing lifecycle, tool selection,\n * real-time pointer capture, ShapeNode creation, and undoable history actions.\n */\nimport type { ArrangeHistoryTarget } from './arrangeController.js';\nimport type { CanvasNode, Point } from './types.js';\nimport { isLocked } from './arrange.js';\nimport {\n\tcreateDefaultStrokeStyle,\n\tfindStrokesAtPoint,\n\tstreamlinePoints,\n\tstrokeToShapeNode,\n\ttype FreehandDrawOptions,\n\ttype FreehandPenSample,\n\ttype FreehandStroke,\n\ttype FreehandStrokeStyle,\n\ttype FreehandTool\n} from './freehand.js';\nimport { listCanvasBrushes, loadCanvasBrushstrokeRenderer, validateCanvasBrush, type CanvasBrush, type CanvasBrushPaint } from './brushstroke.js';\n\n/** A stroke drawn by code or an agent (`drawStroke`). */\nexport interface DrawStrokeInput {\n\t/** Points in canvas (flow) coordinates, at least one. */\n\tpoints: readonly Point[];\n\t/** `pen` (default) or `highlighter`: picks the default style. */\n\ttool?: 'pen' | 'highlighter';\n\t/** Style over the tool's defaults (colour, width, opacity, cap, join, brush, paint). */\n\tstyle?: Partial<FreehandStrokeStyle>;\n\t/** Pressure 0..1 spread evenly along the stroke (one value is constant). Brushed strokes only. */\n\tpressure?: readonly number[];\n\t/** Pen tilt in degrees (−90..90) spread evenly along the stroke. Brushed strokes only. */\n\ttiltX?: readonly number[];\n\ttiltY?: readonly number[];\n\t/** Smooth the points before fitting (default false: agent points are taken as given). */\n\tstreamline?: boolean;\n}\n\nexport interface DrawStrokeResult<TNode extends CanvasNode = CanvasNode> {\n\tid: string;\n\tnode: TNode;\n\t/** The new node's box in canvas coordinates. */\n\tbounds: { x: number; y: number; width: number; height: number };\n\t/** The brush of a brushed stroke, with the profiles it stored. */\n\tbrush?: unknown;\n}\n\nexport interface CanvasDrawControllerOptions<TNode extends CanvasNode = CanvasNode> {\n\tgetNodes: () => TNode[];\n\tsetNodes: (nodes: TNode[]) => void;\n\thistory?: ArrangeHistoryTarget;\n\ttool?: FreehandTool;\n\tstyle?: Partial<FreehandStrokeStyle>;\n\tdrawOptions?: FreehandDrawOptions;\n\tgenerateNodeId?: () => string;\n\tonToolChange?: (tool: FreehandTool) => void;\n\tonStyleChange?: (style: FreehandStrokeStyle) => void;\n\tonActiveStrokeChange?: (stroke: FreehandStroke | null) => void;\n\tonStrokeComplete?: (node: TNode) => void;\n\tonNodesErase?: (erasedNodes: TNode[]) => void;\n}\n\nexport function createCanvasDrawController<TNode extends CanvasNode = CanvasNode>(\n\toptions: CanvasDrawControllerOptions<TNode>\n) {\n\tlet currentTool: FreehandTool = options.tool ?? 'select';\n\tlet currentStyle: FreehandStrokeStyle = {\n\t\t...createDefaultStrokeStyle(currentTool),\n\t\t...options.style\n\t};\n\tlet activeStroke: FreehandStroke | null = null;\n\tconst erasedInDrag = new Map<string, { node: TNode; index: number }>();\n\n\tlet dragStartNodes: TNode[] | null = null;\n\n\tfunction setTool(tool: FreehandTool) {\n\t\tif (currentTool === tool) return;\n\t\tcancelStroke();\n\t\tcurrentTool = tool;\n\t\t// Update default styles when switching between pen and highlighter\n\t\tif (tool === 'pen' || tool === 'highlighter') {\n\t\t\tcurrentStyle = {\n\t\t\t\t...createDefaultStrokeStyle(tool),\n\t\t\t\t...options.style\n\t\t\t};\n\t\t\toptions.onStyleChange?.(currentStyle);\n\t\t}\n\t\toptions.onToolChange?.(tool);\n\t}\n\n\tfunction setStyle(style: Partial<FreehandStrokeStyle>) {\n\t\tcurrentStyle = { ...currentStyle, ...style };\n\t\toptions.onStyleChange?.(currentStyle);\n\t}\n\n\tfunction startStroke(point: Point, pen?: FreehandPenSample) {\n\t\tif (currentTool === 'select') return;\n\n\t\tif (currentTool === 'eraser') {\n\t\t\terasedInDrag.clear();\n\t\t\tdragStartNodes = [...options.getNodes()];\n\t\t\teraseAt(point);\n\t\t\treturn;\n\t\t}\n\n\t\tconst id =\n\t\t\toptions.generateNodeId?.() ??\n\t\t\t`stroke-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;\n\n\t\tactiveStroke = {\n\t\t\tid,\n\t\t\ttool: currentTool,\n\t\t\tpoints: [point],\n\t\t\t...(pen ? { samples: [{ ...pen }] } : {}),\n\t\t\tstyle: { ...currentStyle },\n\t\t\tstartTime: Date.now()\n\t\t};\n\n\t\toptions.onActiveStrokeChange?.(activeStroke);\n\t}\n\n\tfunction continueStroke(point: Point, pen?: FreehandPenSample) {\n\t\tif (currentTool === 'eraser') {\n\t\t\teraseAt(point);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!activeStroke) return;\n\n\t\tconst points = activeStroke.points;\n\t\tconst last = points[points.length - 1];\n\t\t// Discard identical or micro jitter points (sub-pixel)\n\t\tif (Math.hypot(point[0] - last[0], point[1] - last[1]) < 0.25) return;\n\n\t\tpoints.push(point);\n\t\tif (pen && !activeStroke.samples) activeStroke.samples = points.slice(0, -1).map(() => ({}));\n\t\tactiveStroke.samples?.push(pen ? { ...pen } : {});\n\t\toptions.onActiveStrokeChange?.({ ...activeStroke, points: [...points] });\n\t}\n\n\tfunction endStroke(): TNode | null {\n\t\tif (currentTool === 'eraser') {\n\t\t\tconst startSnapshot = dragStartNodes;\n\t\t\tdragStartNodes = null;\n\t\t\tif (erasedInDrag.size > 0 && options.history && startSnapshot) {\n\t\t\t\tconst erasedIds = new Set(Array.from(erasedInDrag.keys()));\n\n\t\t\t\toptions.history.execute({\n\t\t\t\t\tname: 'Erase strokes',\n\t\t\t\t\texecute: () => {\n\t\t\t\t\t\tconst remaining = options.getNodes().filter((n) => !erasedIds.has(n.id));\n\t\t\t\t\t\toptions.setNodes(remaining as TNode[]);\n\t\t\t\t\t},\n\t\t\t\t\tundo: () => {\n\t\t\t\t\t\toptions.setNodes(startSnapshot);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\terasedInDrag.clear();\n\t\t\treturn null;\n\t\t}\n\n\t\tif (!activeStroke) return null;\n\n\t\tconst stroke = activeStroke;\n\t\tactiveStroke = null;\n\t\toptions.onActiveStrokeChange?.(null);\n\n\t\tif (stroke.points.length === 0) return null;\n\n\t\tconst drawOpts = options.drawOptions ?? {};\n\t\tconst smoothed = streamlinePoints(stroke.points, drawOpts.streamline ?? 0.35);\n\t\tconst node = strokeToShapeNode(\n\t\t\t{ ...stroke, points: smoothed },\n\t\t\t{\n\t\t\t\ttolerance: drawOpts.tolerance ?? 1.0,\n\t\t\t\tcornerAngleDeg: drawOpts.cornerAngleDeg ?? 40\n\t\t\t}\n\t\t) as TNode;\n\n\t\taddStrokeNode(node, `Draw ${stroke.tool}`);\n\t\toptions.onStrokeComplete?.(node);\n\t\treturn node;\n\t}\n\n\tfunction addStrokeNode(node: TNode, name: string) {\n\t\tif (options.history) {\n\t\t\toptions.history.execute({\n\t\t\t\tname,\n\t\t\t\texecute: () => {\n\t\t\t\t\toptions.setNodes([...options.getNodes(), node]);\n\t\t\t\t},\n\t\t\t\tundo: () => {\n\t\t\t\t\toptions.setNodes(options.getNodes().filter((n) => n.id !== node.id) as TNode[]);\n\t\t\t\t}\n\t\t\t});\n\t\t} else {\n\t\t\toptions.setNodes([...options.getNodes(), node]);\n\t\t}\n\t}\n\n\t/**\n\t * Draws a whole stroke at once (for agents and code): validates the points, the brush and its\n\t * paint (an unknown or wet brush, or bad gradient paint, is refused before anything changes),\n\t * adds the stroke node as one undo step and returns its id and bounds. Brushed strokes load the paint engine first, so this is\n\t * async.\n\t */\n\tasync function drawStroke(input: DrawStrokeInput): Promise<DrawStrokeResult<TNode>> {\n\t\tconst points = input.points;\n\t\tif (!Array.isArray(points) || points.length === 0) throw new Error('drawStroke needs at least one point ([x, y] in canvas coordinates).');\n\t\tfor (const p of points) {\n\t\t\tif (!Array.isArray(p) || p.length < 2 || !Number.isFinite(p[0]) || !Number.isFinite(p[1])) {\n\t\t\t\tthrow new Error('Every point must be [x, y] with finite numbers (canvas coordinates).');\n\t\t\t}\n\t\t}\n\t\tconst tool = input.tool ?? 'pen';\n\t\tif (tool !== 'pen' && tool !== 'highlighter') throw new Error(`Unknown tool '${String(tool)}': use 'pen' or 'highlighter'.`);\n\t\tconst style: FreehandStrokeStyle = { ...createDefaultStrokeStyle(tool), ...input.style };\n\t\tif (!(style.width > 0)) throw new Error('width must be a positive number of canvas units.');\n\t\tconst profiles = { pressure: input.pressure, tiltX: input.tiltX, tiltY: input.tiltY };\n\t\tfor (const [key, values] of Object.entries(profiles)) {\n\t\t\tif (values === undefined) continue;\n\t\t\tconst [lo, hi] = key === 'pressure' ? [0, 1] : [-90, 90];\n\t\t\tif (!Array.isArray(values) || values.length === 0 || values.some((v) => !Number.isFinite(v) || v < lo || v > hi)) {\n\t\t\t\tthrow new Error(`${key} must be a non-empty list of numbers in ${lo}..${hi}.`);\n\t\t\t}\n\t\t}\n\t\tif ((input.pressure || input.tiltX || input.tiltY) && style.brush === undefined) {\n\t\t\tthrow new Error('pressure and tilt profiles only shape brushed strokes: pass a brush too (see canvas.listBrushes).');\n\t\t}\n\t\tif (style.paint !== undefined && style.brush === undefined) {\n\t\t\tthrow new Error('paint (gradient paint) only colours brushed strokes: pass a brush too (see canvas.listBrushes), or use color for a plain stroke.');\n\t\t}\n\t\tif (style.brush !== undefined) await validateCanvasBrush(style.brush as CanvasBrush);\n\t\t// Store the paint normalised (lowercase #rrggbb, defaults filled in) so the result shows what renders.\n\t\tif (style.paint !== undefined) style.paint = (await loadCanvasBrushstrokeRenderer()).resolveCanvasPaint(style.paint) as CanvasBrushPaint;\n\n\t\tconst id = options.generateNodeId?.() ?? `stroke-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;\n\t\tconst smoothed = input.streamline ? streamlinePoints(points, options.drawOptions?.streamline ?? 0.35) : [...points];\n\t\tconst node = strokeToShapeNode(\n\t\t\t{ id, tool, points: smoothed, style },\n\t\t\t{ tolerance: options.drawOptions?.tolerance ?? 1.0, cornerAngleDeg: options.drawOptions?.cornerAngleDeg ?? 40 }\n\t\t) as TNode;\n\t\tconst shape = (node.data as { shape: { brush?: Record<string, unknown> } }).shape;\n\t\tif (shape.brush) {\n\t\t\tfor (const [key, values] of Object.entries(profiles)) if (values) shape.brush[key] = [...values];\n\t\t}\n\t\taddStrokeNode(node, `Draw ${tool}`);\n\t\toptions.onStrokeComplete?.(node);\n\t\tconst pos = node.position ?? { x: 0, y: 0 };\n\t\treturn {\n\t\t\tid: node.id,\n\t\t\tnode,\n\t\t\tbounds: { x: pos.x, y: pos.y, width: Number(node.width ?? 0), height: Number(node.height ?? 0) },\n\t\t\t...(shape.brush ? { brush: shape.brush } : {})\n\t\t};\n\t}\n\n\tfunction cancelStroke() {\n\t\tif (activeStroke) {\n\t\t\tactiveStroke = null;\n\t\t\toptions.onActiveStrokeChange?.(null);\n\t\t}\n\t\tif (erasedInDrag.size > 0) {\n\t\t\t// Restore nodes erased during this aborted drag if any\n\t\t\tconst entries = Array.from(erasedInDrag.values());\n\t\t\tconst current = [...options.getNodes()];\n\t\t\tconst sorted = [...entries].sort((a, b) => a.index - b.index);\n\t\t\tfor (const { node, index } of sorted) {\n\t\t\t\tconst insertAt = Math.min(index, current.length);\n\t\t\t\tcurrent.splice(insertAt, 0, node);\n\t\t\t}\n\t\t\toptions.setNodes(current);\n\t\t\terasedInDrag.clear();\n\t\t}\n\t}\n\n\tfunction eraseAt(point: Point, radius = 12): TNode[] {\n\t\tconst nodes = options.getNodes();\n\t\tconst candidates = findStrokesAtPoint(point, nodes, radius);\n\t\tconst newlyErased: TNode[] = [];\n\n\t\tfor (const candidate of candidates) {\n\t\t\tif (erasedInDrag.has(candidate.id) || isLocked(candidate)) continue;\n\n\t\t\tconst index = nodes.findIndex((n) => n.id === candidate.id);\n\t\t\tif (index !== -1) {\n\t\t\t\terasedInDrag.set(candidate.id, { node: candidate as TNode, index });\n\t\t\t\tnewlyErased.push(candidate as TNode);\n\t\t\t}\n\t\t}\n\n\t\tif (newlyErased.length > 0) {\n\t\t\tconst erasedSet = new Set(newlyErased.map((n) => n.id));\n\t\t\tconst nextNodes = nodes.filter((n) => !erasedSet.has(n.id)) as TNode[];\n\t\t\toptions.setNodes(nextNodes);\n\t\t\toptions.onNodesErase?.(newlyErased);\n\t\t}\n\n\t\treturn newlyErased;\n\t}\n\n\treturn {\n\t\tget tool() {\n\t\t\treturn currentTool;\n\t\t},\n\t\tsetTool,\n\t\tget style() {\n\t\t\treturn currentStyle;\n\t\t},\n\t\tsetStyle,\n\t\tget activeStroke() {\n\t\t\treturn activeStroke;\n\t\t},\n\t\tget isDrawing() {\n\t\t\treturn activeStroke !== null;\n\t\t},\n\t\tstartStroke,\n\t\tcontinueStroke,\n\t\tendStroke,\n\t\tcancelStroke,\n\t\teraseAt,\n\t\tdrawStroke,\n\t\t/** The dry paint presets a stroke can be brushed with (loads the paint engine). */\n\t\tlistBrushes: listCanvasBrushes\n\t};\n}\n\nexport type CanvasDrawController = ReturnType<typeof createCanvasDrawController>;\n\n/** Modifier options when computing bounds while dragging/drawing. */\nexport interface CanvasDragBoundsModifiers {\n\t/** If true (e.g. Shift key held), constrain the bounding box to 1:1 aspect ratio. */\n\tconstrain?: boolean;\n\t/** If true (e.g. Alt/Option key held), draw/expand outward from the initial start point as center. */\n\tfromCenter?: boolean;\n}\n\nexport interface CanvasDragBounds {\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n}\n\n/**\n * Computes bounding rectangle from start and current points, supporting\n * `constrain` (aspect ratio 1:1, Shift key) and `fromCenter` (origin as center, Alt/Option key).\n */\nexport function computeDragBounds(\n\tstart: { x: number; y: number },\n\tcurrent: { x: number; y: number },\n\tmodifiers: CanvasDragBoundsModifiers = {}\n): CanvasDragBounds {\n\tlet dx = current.x - start.x;\n\tlet dy = current.y - start.y;\n\n\tif (modifiers.constrain) {\n\t\tconst absDx = Math.abs(dx);\n\t\tconst absDy = Math.abs(dy);\n\t\tconst maxDim = Math.max(absDx, absDy);\n\t\tdx = (dx < 0 ? -1 : 1) * maxDim;\n\t\tdy = (dy < 0 ? -1 : 1) * maxDim;\n\t}\n\n\tif (modifiers.fromCenter) {\n\t\tconst halfW = Math.abs(dx);\n\t\tconst halfH = Math.abs(dy);\n\t\treturn {\n\t\t\tx: start.x - halfW,\n\t\t\ty: start.y - halfH,\n\t\t\twidth: halfW * 2,\n\t\t\theight: halfH * 2\n\t\t};\n\t}\n\n\treturn {\n\t\tx: Math.min(start.x, start.x + dx),\n\t\ty: Math.min(start.y, start.y + dy),\n\t\twidth: Math.abs(dx),\n\t\theight: Math.abs(dy)\n\t};\n}\n\n/**\n * Computes bounding rectangle from start point and raw dimensions (dx, dy).\n */\nexport function computeDragBoundsFromPoints(\n\tstartX: number,\n\tstartY: number,\n\tcurrentX: number,\n\tcurrentY: number,\n\tmodifiers: CanvasDragBoundsModifiers = {}\n): CanvasDragBounds {\n\treturn computeDragBounds({ x: startX, y: startY }, { x: currentX, y: currentY }, modifiers);\n}\n","import dagre from \"@dagrejs/dagre\";\nimport type { DiagramFamily, DiagramGraph, DiagramMeta, GraphNode, Point } from \"../../diagram/types.js\";\nimport { getFamilyLayout } from \"../../diagram/layout-registry.js\";\n\n/**\n\n * Automatic layout of a Mermaid diagram graph with dagre, honouring nested groups and per-group\n * direction overrides. Pure functions — no React, no DOM.\n *\n * @module\n */\n\nexport interface DiagramSize {\n width: number;\n height: number;\n}\n\nexport interface DiagramLayoutOptions {\n /** Node size estimator; defaults to {@link estimateDiagramNodeSize}. */\n measure?: (node: GraphNode) => DiagramSize;\n /** Gap between nodes in a rank, in px (default 48). */\n nodeSep?: number;\n /** Gap between ranks, in px (default 72). */\n rankSep?: number;\n /** Padding inside groups around their children (default 24). */\n groupPadding?: number;\n /** Height reserved for a group's title (default 32). */\n groupTitleHeight?: number;\n /**\n * Diagram family. A family that registered its own layout (`registerFamilyLayout`:\n * pie, quadrant, sequence, Gantt…) is laid out from its data instead of with dagre.\n */\n family?: DiagramFamily;\n}\n\nexport const DEFAULT_DIAGRAM_NODE_WIDTH = 160;\nexport const DEFAULT_DIAGRAM_NODE_HEIGHT = 56;\n\n/** Rough text-metric size estimate (no DOM). Hosts can supply real measurements. */\nexport function estimateDiagramNodeSize(node: GraphNode): DiagramSize {\n const lines = String(node.label ?? node.id).split(/<br\\s*\\/?>|\\n/);\n const longest = Math.max(...lines.map((l) => l.replace(/<[^>]+>/g, \"\").length), 1);\n let width = Math.min(360, Math.max(DEFAULT_DIAGRAM_NODE_WIDTH, longest * 8 + 48));\n let height = Math.max(DEFAULT_DIAGRAM_NODE_HEIGHT, lines.length * 20 + 28);\n const extra = extraLines(node);\n if (extra) height += extra * 18 + 12;\n switch (node.shape) {\n case \"actor\":\n case \"c4-person\":\n height = Math.round(height * 1.5);\n break;\n case \"point\":\n width = Math.max(72, longest * 7 + 36);\n height = 30;\n break;\n case \"commit\": {\n const d = Math.max(56, Math.min(120, longest * 7 + 24));\n width = d;\n height = d;\n break;\n }\n case \"diam\":\n case \"hex\":\n case \"trap-b\":\n case \"trap-t\":\n case \"lean-r\":\n case \"lean-l\":\n width = Math.round(width * 1.25);\n height = Math.round(height * 1.4);\n break;\n case \"circle\":\n case \"dbl-circ\":\n case \"sm-circ\":\n case \"fr-circ\":\n case \"f-circ\":\n case \"cross-circ\": {\n const d = Math.max(width * 0.75, height * 1.3);\n width = Math.round(d);\n height = Math.round(d);\n break;\n }\n case \"stateStart\":\n case \"stateEnd\":\n width = 28;\n height = 28;\n break;\n case \"fork\":\n case \"join\":\n width = 120;\n height = 10;\n break;\n case \"choice\":\n width = 48;\n height = 48;\n break;\n case \"cyl\":\n case \"h-cyl\":\n case \"lin-cyl\":\n height = Math.round(height * 1.3);\n break;\n case \"image\": {\n // The picture at its w × h (Mermaid's keys), with the label above or below it.\n const picture = diagramImageSize(node);\n const label = String(node.label ?? \"\").trim() ? IMAGE_LABEL_HEIGHT : 0;\n width = Math.max(picture.width, label ? Math.min(360, longest * 7 + 16) : 0);\n height = picture.height + label;\n break;\n }\n default:\n break;\n }\n return { width, height };\n}\n\n/** Height of an image node's label line. */\nconst IMAGE_LABEL_HEIGHT = 24;\n\n/** An image node's picture size: Mermaid's `w` / `h`, else 96 square (or `w` square). */\nexport function diagramImageSize(node: GraphNode): DiagramSize {\n const num = (v: unknown) => (typeof v === \"number\" && v > 0 ? v : undefined);\n const w = num(node.data?.w);\n const h = num(node.data?.h);\n return { width: w ?? h ?? 96, height: h ?? w ?? 96 };\n}\n\nfunction extraLines(node: GraphNode): number {\n const data = node.data ?? {};\n const members = Array.isArray(data.members) ? data.members.length : 0;\n const attributes = Array.isArray(data.attributes) ? data.attributes.length : 0;\n const descriptions = Array.isArray(data.descriptions) ? data.descriptions.length : 0;\n const details = Array.isArray(data.details) ? data.details.length : 0;\n return members + attributes + descriptions + details;\n}\n\nexport interface DiagramLayout {\n /** Top-left positions in absolute canvas coordinates. */\n positions: Record<string, Point>;\n /** Sizes for nodes and groups. */\n sizes: Record<string, DiagramSize>;\n}\n\nfunction rankdir(direction: string): \"TB\" | \"BT\" | \"LR\" | \"RL\" {\n const d = direction.toUpperCase();\n if (d === \"TD\") return \"TB\";\n if (d === \"TB\" || d === \"BT\" || d === \"LR\" || d === \"RL\") return d;\n return \"TB\";\n}\n\n/**\n * Lays out a graph. Nested groups are laid out recursively: each group's\n * children are arranged with the group's own direction, the group then takes\n * part in its parent's layout as a single box.\n */\nexport function autoLayoutDiagram(graph: DiagramGraph, options: DiagramLayoutOptions = {}): DiagramLayout {\n const measure = options.measure ?? estimateDiagramNodeSize;\n const custom = getFamilyLayout(options.family);\n if (custom) return custom(graph, { v: 1 }, { ...options, measure });\n const nodeSep = options.nodeSep ?? 48;\n const rankSep = options.rankSep ?? 72;\n const pad = options.groupPadding ?? 24;\n const titleH = options.groupTitleHeight ?? 32;\n\n const childrenOf = new Map<string | undefined, { id: string; isGroup: boolean }[]>();\n for (const n of graph.nodes) push(childrenOf, n.parentId, { id: n.id, isGroup: false });\n for (const g of graph.groups) push(childrenOf, g.parentId, { id: g.id, isGroup: true });\n const nodeById = new Map(graph.nodes.map((n) => [n.id, n]));\n const groupById = new Map(graph.groups.map((g) => [g.id, g]));\n\n const sizes: Record<string, DiagramSize> = {};\n const relative: Record<string, Point> = {}; // position relative to parent\n\n const layoutScope = (scope: string | undefined, direction: string): DiagramSize => {\n const children = childrenOf.get(scope) ?? [];\n // Lay out groups first so their size is known.\n for (const c of children) {\n if (c.isGroup) {\n const g = groupById.get(c.id)!;\n const inner = layoutScope(c.id, g.direction ?? direction);\n sizes[c.id] = { width: inner.width + pad * 2, height: inner.height + pad * 2 + titleH };\n } else {\n sizes[c.id] = measure(nodeById.get(c.id)!);\n }\n }\n if (!children.length) return { width: 120, height: 40 };\n\n const g = new dagre.graphlib.Graph({ multigraph: true });\n g.setGraph({\n rankdir: rankdir(direction),\n nodesep: nodeSep,\n ranksep: rankSep,\n marginx: 0,\n marginy: 0,\n });\n g.setDefaultEdgeLabel(() => ({}));\n const ids = new Set(children.map((c) => c.id));\n for (const c of children)\n g.setNode(c.id, { width: sizes[c.id]!.width, height: sizes[c.id]!.height });\n\n // Edges between members of this scope (lifting endpoints to the member that contains them).\n const lift = (id: string): string | null => {\n let cur: string | undefined = id;\n const seen = new Set<string>();\n while (cur && !seen.has(cur)) {\n if (ids.has(cur)) return cur;\n seen.add(cur);\n cur = nodeById.get(cur)?.parentId ?? groupById.get(cur)?.parentId;\n }\n return null;\n };\n graph.edges.forEach((e, i) => {\n const s = lift(e.source);\n const t = lift(e.target);\n if (s && t && s !== t) g.setEdge(s, t, { minlen: Math.max(1, e.length) }, `e${i}`);\n });\n dagre.layout(g);\n\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n for (const c of children) {\n const n = g.node(c.id);\n const w = sizes[c.id]!.width;\n const h = sizes[c.id]!.height;\n const x = n.x - w / 2;\n const y = n.y - h / 2;\n relative[c.id] = [x, y];\n minX = Math.min(minX, x);\n minY = Math.min(minY, y);\n maxX = Math.max(maxX, x + w);\n maxY = Math.max(maxY, y + h);\n }\n // Normalise so the scope's content starts at (0,0).\n for (const c of children) {\n const [x, y] = relative[c.id]!;\n relative[c.id] = [x - minX, y - minY];\n }\n return { width: maxX - minX, height: maxY - minY };\n };\n\n layoutScope(undefined, graph.direction);\n\n // Convert relative → absolute.\n const positions: Record<string, Point> = {};\n const absolute = (id: string): Point => {\n if (positions[id]) return positions[id]!;\n const parentId = nodeById.get(id)?.parentId ?? groupById.get(id)?.parentId;\n const [rx, ry] = relative[id] ?? [0, 0];\n if (!parentId) {\n positions[id] = [rx, ry];\n } else {\n const [px, py] = absolute(parentId);\n positions[id] = [px + pad + rx, py + pad + titleH + ry];\n }\n return positions[id]!;\n };\n for (const n of graph.nodes) absolute(n.id);\n for (const g of graph.groups) absolute(g.id);\n\n return { positions, sizes };\n}\n\nfunction push<K, V>(map: Map<K, V[]>, key: K, value: V): void {\n const arr = map.get(key);\n if (arr) arr.push(value);\n else map.set(key, [value]);\n}\n\n/**\n * Returns positions for every node/group: stored metadata where available,\n * auto-layout for the rest. New nodes are placed near their neighbours so a\n * partially laid-out diagram does not jump.\n */\nexport function ensureDiagramLayout(\n graph: DiagramGraph,\n meta: DiagramMeta,\n options?: DiagramLayoutOptions,\n): DiagramLayout {\n const measure = options?.measure ?? estimateDiagramNodeSize;\n const custom = getFamilyLayout(options?.family);\n if (custom) return custom(graph, meta, { ...(options ?? {}), measure });\n const stored = meta.layout ?? {};\n const allIds = [...graph.nodes.map((n) => n.id), ...graph.groups.map((g) => g.id)];\n const missing = allIds.filter((id) => !stored[id]);\n const sizes: Record<string, DiagramSize> = {};\n for (const n of graph.nodes)\n sizes[n.id] = meta.sizes?.[n.id]\n ? { width: meta.sizes[n.id]![0], height: meta.sizes[n.id]![1] }\n : measure(n);\n\n if (!missing.length) {\n const positions: Record<string, Point> = {};\n for (const id of allIds) positions[id] = stored[id]!;\n for (const g of graph.groups) sizes[g.id] = diagramGroupSize(g.id, graph, positions, sizes, meta);\n return { positions, sizes };\n }\n\n const auto = autoLayoutDiagram(graph, options);\n if (missing.length === allIds.length) return auto;\n\n // Blend: keep stored positions, place missing ones relative to auto layout\n // by aligning on the centroid of the nodes that have both.\n const both = allIds.filter((id) => stored[id] && auto.positions[id]);\n let dx = 0;\n let dy = 0;\n if (both.length) {\n for (const id of both) {\n dx += stored[id]![0] - auto.positions[id]![0];\n dy += stored[id]![1] - auto.positions[id]![1];\n }\n dx /= both.length;\n dy /= both.length;\n }\n const positions: Record<string, Point> = {};\n for (const id of allIds) {\n positions[id] = stored[id] ?? [auto.positions[id]![0] + dx, auto.positions[id]![1] + dy];\n }\n for (const g of graph.groups) sizes[g.id] = diagramGroupSize(g.id, graph, positions, sizes, meta);\n return { positions, sizes };\n}\n\n/** Bounding box of a group's descendants, with padding. */\nexport function diagramGroupSize(\n groupId: string,\n graph: DiagramGraph,\n positions: Record<string, Point>,\n sizes: Record<string, DiagramSize>,\n meta: DiagramMeta,\n pad = 24,\n titleH = 32,\n): DiagramSize {\n if (meta.sizes?.[groupId])\n return { width: meta.sizes[groupId]![0], height: meta.sizes[groupId]![1] };\n const members = [\n ...graph.nodes.filter((n) => n.parentId === groupId).map((n) => n.id),\n ...graph.groups.filter((g) => g.parentId === groupId).map((g) => g.id),\n ];\n if (!members.length) return { width: 200, height: 100 };\n const [gx, gy] = positions[groupId] ?? [0, 0];\n let maxX = 0;\n let maxY = 0;\n for (const id of members) {\n const size =\n sizes[id] ??\n (graph.groups.some((g) => g.id === id)\n ? diagramGroupSize(id, graph, positions, sizes, meta, pad, titleH)\n : { width: DEFAULT_DIAGRAM_NODE_WIDTH, height: DEFAULT_DIAGRAM_NODE_HEIGHT });\n sizes[id] = size;\n const [x, y] = positions[id] ?? [gx, gy];\n maxX = Math.max(maxX, x - gx + size.width);\n maxY = Math.max(maxY, y - gy + size.height);\n }\n return { width: maxX + pad, height: maxY + pad };\n}\n","import type {\n ArrowHead,\n DiagramGraph,\n EdgePattern,\n GraphEdge,\n GraphGroup,\n GraphNode,\n Point,\n} from \"../../diagram/types.js\";\nimport type { DiagramLayout, DiagramSize } from \"./layout.js\";\n\n/**\n * Projection of a Mermaid diagram graph onto canvas (React Flow / Svelte Flow) nodes and edges.\n * Kept free of React so it can be unit-tested and reused (e.g. for export).\n *\n * @module\n */\n\nexport interface MermaidNodeData extends Record<string, unknown> {\n node: GraphNode;\n family: string;\n /** Resolved `classDef` styles for the node's classes, merged with inline styles. */\n resolvedStyles: Record<string, string>;\n editable: boolean;\n}\n\nexport interface MermaidGroupData extends Record<string, unknown> {\n group: GraphGroup;\n family: string;\n resolvedStyles: Record<string, string>;\n editable: boolean;\n}\n\nexport interface MermaidEdgeData extends Record<string, unknown> {\n edge: GraphEdge;\n family: string;\n resolvedStyles: Record<string, string>;\n editable: boolean;\n}\n\nexport interface MermaidFlowNode {\n id: string;\n /** `mermaid-node` / `mermaid-group`, or a family canvas node type. */\n type: string;\n position: { x: number; y: number };\n data: MermaidNodeData | MermaidGroupData | Record<string, unknown>;\n parentId?: string;\n width: number;\n height: number;\n style?: Record<string, string | number>;\n draggable: boolean;\n selectable: boolean;\n connectable: boolean;\n zIndex: number;\n extent?: \"parent\";\n className?: string;\n}\n\nexport interface MermaidFlowEdge {\n id: string;\n /** `mermaid-edge`, or a family canvas edge type. */\n type: string;\n source: string;\n target: string;\n data: MermaidEdgeData;\n label?: string;\n selectable: boolean;\n reconnectable: boolean;\n zIndex: number;\n sourceHandle?: string;\n targetHandle?: string;\n className?: string;\n}\n\n/** Parses `fill:#f9f,stroke:#333,stroke-width:4px` into an object. */\nexport function parseMermaidStyles(styles: string[]): Record<string, string> {\n const out: Record<string, string> = {};\n for (const s of styles) {\n const idx = s.indexOf(\":\");\n if (idx === -1) continue;\n out[s.slice(0, idx).trim()] = s.slice(idx + 1).trim();\n }\n return out;\n}\n\nexport function resolveMermaidStyles(\n classes: string[],\n inline: string[],\n classDefs: Record<string, string[]>,\n): Record<string, string> {\n let out: Record<string, string> = {};\n if (classDefs.default) out = { ...out, ...parseMermaidStyles(classDefs.default) };\n for (const cls of classes)\n if (classDefs[cls]) out = { ...out, ...parseMermaidStyles(classDefs[cls]!) };\n return { ...out, ...parseMermaidStyles(inline) };\n}\n\nfunction depth(id: string, parents: Map<string, string | undefined>): number {\n let d = 0;\n let cur = parents.get(id);\n const seen = new Set<string>();\n while (cur && !seen.has(cur)) {\n seen.add(cur);\n d++;\n cur = parents.get(cur);\n }\n return d;\n}\n\nexport interface MermaidFlowOptions {\n editable?: boolean;\n /** Override positions (e.g. during a drag). Absolute coordinates. */\n positions?: Record<string, Point>;\n sizes?: Record<string, DiagramSize>;\n}\n\n/**\n * Converts the graph + layout into React Flow nodes. Positions in the layout\n * are absolute; React Flow wants child positions relative to the parent, so\n * they are converted here.\n */\nexport function mermaidFlowNodes(\n graph: DiagramGraph,\n layout: DiagramLayout,\n options: MermaidFlowOptions = {},\n): MermaidFlowNode[] {\n const editable = options.editable ?? false;\n const positions = { ...layout.positions, ...(options.positions ?? {}) };\n const sizes = { ...layout.sizes, ...(options.sizes ?? {}) };\n const parents = new Map<string, string | undefined>();\n for (const n of graph.nodes) parents.set(n.id, n.parentId);\n for (const g of graph.groups) parents.set(g.id, g.parentId);\n\n const rel = (id: string, parentId: string | undefined): { x: number; y: number } => {\n const [x, y] = positions[id] ?? [0, 0];\n if (!parentId) return { x, y };\n const [px, py] = positions[parentId] ?? [0, 0];\n return { x: x - px, y: y - py };\n };\n\n const groups: MermaidFlowNode[] = graph.groups.map((g) => {\n const size = sizes[g.id] ?? { width: 240, height: 160 };\n return {\n id: g.id,\n type: \"mermaid-group\",\n position: rel(g.id, g.parentId),\n parentId: g.parentId,\n data: {\n group: g,\n family: \"\",\n resolvedStyles: resolveMermaidStyles(g.classes, g.styles, graph.classDefs),\n editable,\n },\n width: size.width,\n height: size.height,\n style: { width: size.width, height: size.height },\n draggable: editable && !g.data?.locked,\n selectable: editable,\n connectable: editable && !g.data?.locked,\n zIndex: depth(g.id, parents),\n };\n });\n\n const nodes: MermaidFlowNode[] = graph.nodes.map((n) => {\n const size = sizes[n.id] ?? { width: 160, height: 56 };\n return {\n id: n.id,\n type: \"mermaid-node\",\n position: rel(n.id, n.parentId),\n parentId: n.parentId,\n data: {\n node: n,\n family: \"\",\n resolvedStyles: resolveMermaidStyles(n.classes, n.styles, graph.classDefs),\n editable,\n },\n width: size.width,\n height: size.height,\n style: { width: size.width, height: size.height },\n draggable: editable && !n.data?.locked,\n selectable: editable,\n connectable: editable && !n.data?.locked && !n.data?.synthetic,\n zIndex: depth(n.id, parents) + 100,\n };\n });\n\n // Parents must precede children for React Flow.\n const order = (a: MermaidFlowNode, b: MermaidFlowNode) => depth(a.id, parents) - depth(b.id, parents);\n return [...groups.sort(order), ...nodes.sort(order)];\n}\n\nexport function mermaidFlowEdges(graph: DiagramGraph, options: MermaidFlowOptions = {}): MermaidFlowEdge[] {\n const editable = options.editable ?? false;\n return graph.edges.map((e) => ({\n id: e.id,\n type: \"mermaid-edge\",\n source: e.source,\n target: e.target,\n label: e.label,\n data: {\n edge: e,\n family: \"\",\n resolvedStyles: resolveMermaidStyles(e.classes, e.styles, graph.classDefs),\n editable,\n },\n selectable: editable,\n reconnectable: editable,\n zIndex: 1000,\n }));\n}\n\n/** SVG marker id for an arrow head (markers are defined once by the canvas). */\nexport function mermaidMarkerId(head: ArrowHead, side: \"start\" | \"end\"): string | undefined {\n if (head === \"none\") return undefined;\n return `pixerate-mermaid-marker-${head}-${side}`;\n}\n\nexport function mermaidEdgeDash(pattern: EdgePattern): string | undefined {\n switch (pattern) {\n case \"dotted\":\n return \"6 4\";\n case \"invisible\":\n return \"0 1\";\n default:\n return undefined;\n }\n}\n\nexport function mermaidEdgeWidth(pattern: EdgePattern): number {\n return pattern === \"thick\" ? 3.5 : 1.75;\n}\n","import type { DiagramGraph, Point } from \"../../diagram/types.js\";\nimport { DEFAULT_DIAGRAM_NODE_HEIGHT, type DiagramLayout, type DiagramSize } from \"./layout.js\";\n\n/**\n * Helpers for showing a laid-out diagram outside the editor: sizing an inline\n * viewer to its content, animating between two layouts, and trimming a graph\n * that is still being streamed in. Pure functions — no React, no DOM.\n *\n * @module\n */\n\nexport interface DiagramBounds {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/** Extent of every positioned node and group, or `null` for an empty layout. */\nexport function diagramLayoutBounds(layout: DiagramLayout): DiagramBounds | null {\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n for (const [id, [x, y]] of Object.entries(layout.positions)) {\n const size = layout.sizes[id] ?? { width: 0, height: 0 };\n minX = Math.min(minX, x);\n minY = Math.min(minY, y);\n maxX = Math.max(maxX, x + size.width);\n maxY = Math.max(maxY, y + size.height);\n }\n if (minX === Infinity) return null;\n return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n}\n\nexport interface DiagramCanvasHeightOptions {\n /** Smallest height returned (default 320). */\n min?: number;\n /** Largest height returned; taller content is zoomed out (default 560). */\n max?: number;\n /** Room added around the content (default 48). */\n padding?: number;\n}\n\nfunction clampHeight(content: number, options: DiagramCanvasHeightOptions = {}): number {\n const { min = 320, max = 560, padding = 48 } = options;\n return Math.round(Math.min(max, Math.max(min, content + padding)));\n}\n\n/** Container height that shows a layout at 1:1 where it fits, clamped. */\nexport function diagramCanvasHeight(layout: DiagramLayout, options?: DiagramCanvasHeightOptions): number {\n return clampHeight(diagramLayoutBounds(layout)?.height ?? 0, options);\n}\n\n// Shared with dagre's default rank separation, so the estimate and the real\n// height agree for a simple chain.\nconst RANK_HEIGHT = DEFAULT_DIAGRAM_NODE_HEIGHT + 72;\n// Counts alone cannot tell a chain from a fan-out; both clamp long before the\n// error matters.\nconst NODES_PER_RANK = 2;\nconst GROUP_BAND = 24 * 2 + 32;\n\n/**\n * Height to reserve before the asynchronous layout resolves, from the graph's\n * counts alone. Uses the same clamp as {@link diagramCanvasHeight}, so the container\n * does not jump when the layout lands.\n */\nexport function estimateDiagramCanvasHeight(graph: DiagramGraph, options?: DiagramCanvasHeightOptions): number {\n const ranks = Math.ceil(graph.nodes.length / NODES_PER_RANK);\n return clampHeight(ranks * RANK_HEIGHT + graph.groups.length * GROUP_BAND, options);\n}\n\nfunction mix(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\n\n/**\n * A layout partway between two others, for animating a re-layout: ids in both\n * interpolate at `t` (0 = `from`, 1 = `to`), ids new in `to` start at their\n * target, and ids missing from `to` drop out.\n */\nexport function blendDiagramLayout(from: DiagramLayout, to: DiagramLayout, t: number): DiagramLayout {\n if (t >= 1) return to;\n const positions: Record<string, Point> = {};\n for (const [id, [x, y]] of Object.entries(to.positions)) {\n const origin = from.positions[id];\n positions[id] = origin ? [mix(origin[0], x, t), mix(origin[1], y, t)] : [x, y];\n }\n const sizes: Record<string, DiagramSize> = {};\n for (const [id, size] of Object.entries(to.sizes)) {\n const origin = from.sizes[id];\n sizes[id] = origin\n ? { width: mix(origin.width, size.width, t), height: mix(origin.height, size.height, t) }\n : size;\n }\n return { positions, sizes };\n}\n\n/**\n * The graph without nodes that no edge touches yet. While a diagram streams\n * in, a node usually arrives a line before the edge that places it; showing it\n * alone makes the layout jump twice. Groups are kept when they (or a nested\n * group) still contain a node.\n */\nexport function connectedDiagramOnly(graph: DiagramGraph): DiagramGraph {\n const linked = new Set<string>();\n for (const edge of graph.edges) {\n linked.add(edge.source);\n linked.add(edge.target);\n }\n const nodes = graph.nodes.filter((n) => linked.has(n.id));\n if (nodes.length === graph.nodes.length) return graph;\n const parentOf = new Map(graph.groups.map((g) => [g.id, g.parentId]));\n const keep = new Set<string>();\n for (const node of nodes) {\n let id = node.parentId;\n while (id && !keep.has(id)) {\n keep.add(id);\n id = parentOf.get(id);\n }\n }\n // An edge can name a group as an endpoint (flowchart allows it).\n for (const id of linked) if (parentOf.has(id)) keep.add(id);\n return { ...graph, nodes, groups: graph.groups.filter((g) => keep.has(g.id)) };\n}\n","// Follows Ursula's MermaidStreamingView: nodes are held back until an edge places them,\n// layouts are recomputed only when the structure changes, and the view tweens between\n// layouts with `blendDiagramLayout`.\nimport { parseMermaid } from '../../diagram/document.js';\nimport type { DiagramGraph, MermaidDocument } from '../../diagram/types.js';\nimport { ensureDiagramLayout, type DiagramLayout, type DiagramLayoutOptions } from './layout.js';\nimport { connectedDiagramOnly } from './view.js';\n\nexport interface DiagramStreamResult {\n\t/** The diagram as far as it has arrived (its graph trimmed while incomplete). */\n\tdocument: MermaidDocument;\n\t/** Layout for `document.graph`, or `null` before any node can be placed. */\n\tlayout: DiagramLayout | null;\n\t/** True when the layout differs from the previous result (start a tween). */\n\trelaid: boolean;\n}\n\n/** Structure only: labels can change without moving anything. */\nfunction fingerprint(graph: DiagramGraph): string {\n\treturn JSON.stringify([\n\t\tgraph.direction,\n\t\tgraph.nodes.map((n) => [n.id, n.parentId ?? '', n.shape, String(n.label).length > 24 ? 1 : 0]),\n\t\tgraph.edges.map((e) => [e.source, e.target]),\n\t\tgraph.groups.map((g) => [g.id, g.parentId ?? '', g.direction ?? ''])\n\t]);\n}\n\n/**\n * Turns Mermaid text that is still being written (e.g. streamed from a model) into a\n * diagram and a layout to draw. While incomplete, nodes no edge touches yet are held\n * back (a node usually arrives a line before the edge that places it, and showing it\n * alone makes the layout jump twice). Layouts are only recomputed when nodes, edges or\n * groups change, so label text streaming in does not move anything.\n */\nexport class DiagramStream {\n\tprivate lastKey = '';\n\tprivate layout: DiagramLayout | null = null;\n\n\tconstructor(private readonly options: DiagramLayoutOptions = {}) {}\n\n\t/** Reads the source so far. `complete` when nothing more will arrive. */\n\tupdate(source: string, complete = false): DiagramStreamResult {\n\t\tconst text = streamedDiagramText(source);\n\t\tconst parsed = parseMermaid(text);\n\t\tconst document = !complete ? { ...parsed, graph: connectedDiagramOnly(parsed.graph) } : parsed;\n\t\tif (document.tier !== 'editable' || !document.graph.nodes.length) return { document, layout: this.layout && complete ? null : this.layout, relaid: false };\n\t\tconst key = fingerprint(document.graph);\n\t\tif (key === this.lastKey && this.layout) return { document, layout: this.layout, relaid: false };\n\t\tthis.lastKey = key;\n\t\tthis.layout = ensureDiagramLayout(document.graph, document.meta, { family: document.family, ...this.options });\n\t\treturn { document, layout: this.layout, relaid: true };\n\t}\n\n\t/** Forgets the previous layout (a new source). */\n\treset(): void {\n\t\tthis.lastKey = '';\n\t\tthis.layout = null;\n\t}\n}\n\n/**\n * The diagram text in a reply that is still arriving: the body of the first ```mermaid\n * (or unmarked) fence, even before it is closed, or the whole text when there is no fence.\n * Prose before the fence (a model's \"Here is the plan:\") is skipped.\n */\nexport function streamedDiagramText(source: string): string {\n\tconst open = /(?:^|\\n)[ ]{0,3}(`{3,}|~{3,})[ \\t]*(mermaid|mmd)?[^\\n`]*(?:\\n|$)/i.exec(source);\n\tif (!open) return source;\n\tconst rest = source.slice(open.index + open[0].length);\n\tconst fence = open[1]!;\n\tconst close = new RegExp(`(?:^|\\\\n)[ ]{0,3}${fence[0] === '`' ? '`' : '~'}{${fence.length},}[ \\\\t]*(?:\\\\n|$)`).exec(rest);\n\treturn close ? rest.slice(0, close.index) : rest.replace(/\\n[ ]{0,3}[`~]{1,2}$/, '');\n}\n\n/** Ease-out cubic, for layout tweens. */\nexport const easeDiagramTween = (t: number) => 1 - Math.pow(1 - Math.max(0, Math.min(1, t)), 3);\n","/** Concrete colours (no CSS variables), so the SVG draws the same as a file, an `<img>` or a PNG. */\nexport interface DiagramSvgTheme {\n\tbackground: string;\n\tnodeFill: string;\n\tnodeStroke: string;\n\ttext: string;\n\tedge: string;\n\tedgeLabelBackground: string;\n\tgroupFill: string;\n\tgroupStroke: string;\n\tgroupText: string;\n\tfontFamily: string;\n\tfontSize: number;\n}\n\nexport const DIAGRAM_SVG_LIGHT_THEME: DiagramSvgTheme = {\n\tbackground: '#ffffff',\n\tnodeFill: '#ffffff',\n\tnodeStroke: '#0f172a',\n\ttext: '#0f172a',\n\tedge: '#64748b',\n\tedgeLabelBackground: '#ffffff',\n\tgroupFill: '#f8fafc',\n\tgroupStroke: '#cbd5e1',\n\tgroupText: '#475569',\n\tfontFamily: 'system-ui, -apple-system, \"Segoe UI\", sans-serif',\n\tfontSize: 13\n};\n\nexport const DIAGRAM_SVG_DARK_THEME: DiagramSvgTheme = {\n\tbackground: '#0f172a',\n\tnodeFill: '#1e293b',\n\tnodeStroke: '#e2e8f0',\n\ttext: '#f1f5f9',\n\tedge: '#94a3b8',\n\tedgeLabelBackground: '#0f172a',\n\tgroupFill: '#111c33',\n\tgroupStroke: '#334155',\n\tgroupText: '#94a3b8',\n\tfontFamily: 'system-ui, -apple-system, \"Segoe UI\", sans-serif',\n\tfontSize: 13\n};\n","// Family drawings follow Ursula's family canvases (src/react/canvases/*.tsx and\n// src/react/canvas/familyCanvas.ts), drawn as framework-free marks so React, Svelte and the\n// SVG export share one renderer per family.\nimport type { DiagramFamily, DiagramGraph } from '../../diagram/types.js';\nimport type { DiagramLayout } from './layout.js';\nimport { DIAGRAM_SVG_DARK_THEME, DIAGRAM_SVG_LIGHT_THEME, type DiagramSvgTheme } from './theme.js';\n\n/**\n * Colours for family drawings. Concrete values draw the same everywhere (files, PNG); a\n * renderer may pass CSS (`var(--x, #fff)`) since the React and Svelte scenes paint with\n * `style`. `palette` colours slices, series, sections and branches in order.\n */\nexport interface DiagramSceneTheme extends DiagramSvgTheme {\n\t/** Categorical colours (pie slices, sections, branches, curves…), used in order. */\n\tpalette: string[];\n\t/** Text drawn on palette colours. */\n\tpaletteText: string;\n\t/** Axis lines, grid and lifelines. */\n\tgrid: string;\n\t/** Secondary text (axis labels, legends, notes). */\n\tmutedText: string;\n\t/** Selection outline. */\n\tselection: string;\n}\n\nconst PALETTE_LIGHT = ['#4e79a7', '#f28e2b', '#59a14f', '#e15759', '#76b7b2', '#edc948', '#b07aa1', '#ff9da7', '#9c755f', '#bab0ac', '#8cd17d', '#86bcb6'];\nconst PALETTE_DARK = ['#7aa6d6', '#ffad5c', '#7fcf73', '#ff8587', '#9fdcd6', '#ffe07a', '#d4a2c6', '#ffc2c9', '#c49a82', '#d9d0cc', '#b0eba3', '#acd8d3'];\n\nexport const DIAGRAM_SCENE_LIGHT_THEME: DiagramSceneTheme = {\n\t...DIAGRAM_SVG_LIGHT_THEME,\n\tpalette: PALETTE_LIGHT,\n\tpaletteText: '#0f172a',\n\tgrid: '#cbd5e1',\n\tmutedText: '#64748b',\n\tselection: '#2563eb'\n};\n\nexport const DIAGRAM_SCENE_DARK_THEME: DiagramSceneTheme = {\n\t...DIAGRAM_SVG_DARK_THEME,\n\tpalette: PALETTE_DARK,\n\tpaletteText: '#0f172a',\n\tgrid: '#334155',\n\tmutedText: '#94a3b8',\n\tselection: '#60a5fa'\n};\n\ninterface DiagramMarkBase {\n\t/** Unique within the scene (a stable key for frameworks). */\n\tkey: string;\n\t/** What the mark draws, for styling hooks (`slice`, `legend`, `axis`, `lifeline`, `bar`…). */\n\trole: string;\n\t/** The node, edge or group id clicking this mark selects (none: decoration). */\n\ttarget?: string;\n\t/** Mark belongs to the current selection. */\n\tselected?: boolean;\n\topacity?: number;\n}\n\nexport interface DiagramRectMark extends DiagramMarkBase {\n\tkind: 'rect';\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n\tfill: string;\n\tstroke?: string;\n\tstrokeWidth?: number;\n\tradius?: number;\n\tdash?: string;\n}\n\nexport interface DiagramPathMark extends DiagramMarkBase {\n\tkind: 'path';\n\td: string;\n\tfill?: string;\n\tstroke?: string;\n\tstrokeWidth?: number;\n\tdash?: string;\n\t/** Arrow head at the end (`arrow` open, `arrowclosed` filled). */\n\tmarkerEnd?: 'arrow' | 'arrowclosed';\n\tmarkerStart?: 'arrow' | 'arrowclosed';\n\t/** Bounds of the path; required when `d` uses arcs or relative commands. */\n\tbox?: { x: number; y: number; width: number; height: number };\n}\n\nexport interface DiagramCircleMark extends DiagramMarkBase {\n\tkind: 'circle';\n\tcx: number;\n\tcy: number;\n\tr: number;\n\tfill: string;\n\tstroke?: string;\n\tstrokeWidth?: number;\n}\n\nexport interface DiagramLineMark extends DiagramMarkBase {\n\tkind: 'line';\n\tx1: number;\n\ty1: number;\n\tx2: number;\n\ty2: number;\n\tstroke: string;\n\tstrokeWidth?: number;\n\tdash?: string;\n\tmarkerEnd?: 'arrow' | 'arrowclosed';\n\tmarkerStart?: 'arrow' | 'arrowclosed';\n}\n\nexport interface DiagramTextMark extends DiagramMarkBase {\n\tkind: 'text';\n\tx: number;\n\ty: number;\n\ttext: string;\n\tanchor: 'start' | 'middle' | 'end';\n\t/** Vertical alignment of the text to `y`. */\n\tbaseline: 'top' | 'middle' | 'bottom';\n\tsize: number;\n\tweight?: number;\n\tfill: string;\n\t/** Degrees, around (x, y). */\n\trotate?: number;\n\titalic?: boolean;\n}\n\nexport type DiagramMark = DiagramRectMark | DiagramPathMark | DiagramCircleMark | DiagramLineMark | DiagramTextMark;\n\n/** A family's drawing in canvas coordinates (the same as the layout's positions). */\nexport interface DiagramScene {\n\tfamily: DiagramFamily;\n\t/** Bounds of everything drawn. */\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n\t/** Draw in order. */\n\tmarks: DiagramMark[];\n}\n\nexport interface DiagramSceneOptions {\n\ttheme?: Partial<DiagramSceneTheme>;\n\t/** Selected node, edge or group ids (their marks get `selected`). */\n\tselection?: readonly string[];\n}\n\nexport interface FamilySceneInput {\n\tgraph: DiagramGraph;\n\tlayout: DiagramLayout;\n\ttheme: DiagramSceneTheme;\n\tselection: ReadonlySet<string>;\n}\n\n/** Draws a family from its graph and layout; returns marks in canvas coordinates. */\nexport type FamilyScene = (input: FamilySceneInput) => DiagramMark[];\n\n/**\n * `target` of the diagram title's marks: double-clicking it renames the title (`setTitle`)\n * rather than a node.\n */\nexport const DIAGRAM_TITLE_TARGET = '::title';\n\n/** The palette colour for index `i` (cycling). */\nexport function sceneColor(theme: DiagramSceneTheme, i: number): string {\n\treturn theme.palette[((i % theme.palette.length) + theme.palette.length) % theme.palette.length]!;\n}\n\n/** Rough text width (no DOM), in px. */\nexport function estimateTextWidth(text: string, size: number): number {\n\treturn text.length * size * 0.58;\n}\n\n","import { PIE_METRICS, pieGeometry } from '../../../diagram/pie/geometry.js';\nimport { DIAGRAM_TITLE_TARGET, sceneColor, type DiagramMark, type FamilyScene } from '../scene-kit.js';\n\nconst M = PIE_METRICS;\n\n/**\n * Pie charts as Mermaid draws them (Ursula's `canvases/pie.tsx`): wedges in document order\n * with a percentage on each, the outer circle, the legend to the right and the title above.\n * A wedge and its legend row select the slice.\n */\nexport const pieScene: FamilyScene = ({ graph, theme }) => {\n\tconst geo = pieGeometry(graph);\n\tconst marks: DiagramMark[] = [];\n\tfor (const slice of geo.slices) {\n\t\tif (!slice.drawn) continue;\n\t\tconst r = geo.radius;\n\t\tmarks.push({\n\t\t\tkind: 'path',\n\t\t\tkey: `wedge:${slice.id}`,\n\t\t\trole: 'slice',\n\t\t\ttarget: slice.id,\n\t\t\td: slice.path,\n\t\t\tfill: sceneColor(theme, slice.colorIndex),\n\t\t\tstroke: theme.background,\n\t\t\tstrokeWidth: 2,\n\t\t\tbox: { x: geo.cx - r, y: geo.cy - r, width: r * 2, height: r * 2 }\n\t\t});\n\t}\n\tmarks.push({ kind: 'circle', key: 'outer', role: 'outline', cx: geo.cx, cy: geo.cy, r: geo.outerRadius, fill: 'none', stroke: theme.nodeStroke, strokeWidth: M.outerStrokeWidth });\n\tfor (const slice of geo.slices) {\n\t\tif (!slice.drawn) continue;\n\t\tmarks.push({ kind: 'text', key: `percent:${slice.id}`, role: 'slice-label', target: slice.id, x: slice.labelX, y: slice.labelY, text: slice.labelText, anchor: 'middle', baseline: 'bottom', size: 17, fill: theme.paletteText });\n\t}\n\tfor (const entry of geo.legend) {\n\t\tmarks.push(\n\t\t\t{ kind: 'rect', key: `swatch:${entry.sliceId}`, role: 'legend', target: entry.sliceId, x: entry.x, y: entry.y, width: M.legendRectSize, height: M.legendRectSize, fill: sceneColor(theme, entry.colorIndex), stroke: sceneColor(theme, entry.colorIndex) },\n\t\t\t{\n\t\t\t\tkind: 'text',\n\t\t\t\tkey: `legend:${entry.sliceId}`,\n\t\t\t\trole: 'legend',\n\t\t\t\ttarget: entry.sliceId,\n\t\t\t\tx: entry.x + M.legendRectSize + M.legendSpacing,\n\t\t\t\ty: entry.y + M.legendRectSize / 2,\n\t\t\t\ttext: entry.text,\n\t\t\t\tanchor: 'start',\n\t\t\t\tbaseline: 'middle',\n\t\t\t\tsize: M.legendFontSize,\n\t\t\t\tfill: theme.text\n\t\t\t}\n\t\t);\n\t}\n\tif (geo.title)\n\t\tmarks.push({ kind: 'text', key: 'title', role: 'title', target: DIAGRAM_TITLE_TARGET, x: geo.title.x, y: geo.title.y, text: geo.title.text, anchor: 'middle', baseline: 'bottom', size: M.titleFontSize, fill: theme.text });\n\treturn marks;\n};\n","import { layoutChart } from '../../../chart/layout.js';\nimport { createChartSpec } from '../../../chart/spec.js';\nimport type { ChartMark, ChartSelection, ChartSpec, ChartTheme } from '../../../chart/types.js';\nimport type { DiagramGraph } from '../../../diagram/types.js';\nimport { RADAR_CHART_BOX, XY_CHART_BOX } from '../../../diagram/layout-registry.js';\nimport { DIAGRAM_TITLE_TARGET, type DiagramMark, type DiagramSceneTheme, type FamilyScene } from '../scene-kit.js';\n\n/**\n * XY and radar charts, which Ursula leaves to Mermaid's own rendering, are drawn with the\n * chart module (`layoutChart`): each series' bars, lines and points select its node.\n */\n\nconst num = (v: unknown): number | null => (typeof v === 'number' && Number.isFinite(v) ? v : null);\nconst numbers = (v: unknown): (number | null)[] => (Array.isArray(v) ? v.map(num) : []);\nconst text = (v: unknown): string => (typeof v === 'string' ? v : '');\n\nfunction chartTheme(theme: DiagramSceneTheme): Partial<ChartTheme> {\n\treturn {\n\t\tbackground: theme.background,\n\t\tsurface: theme.background,\n\t\ttext: theme.text,\n\t\tmutedText: theme.mutedText,\n\t\tgrid: theme.grid,\n\t\taxis: theme.mutedText,\n\t\tselection: theme.selection,\n\t\tfontFamily: theme.fontFamily\n\t};\n}\n\n/** The chart spec an `xychart-beta` graph describes. */\nexport function xyChartSpec(graph: DiagramGraph, palette?: readonly string[]): ChartSpec {\n\tconst data = graph.data ?? {};\n\tconst longest = Math.max(0, ...graph.nodes.map((n) => numbers(n.data?.values).length));\n\tlet categories = Array.isArray(data.categories) ? data.categories.map(String) : [];\n\tconst xMin = num(data.xMin);\n\tconst xMax = num(data.xMax);\n\tif (!categories.length && longest) {\n\t\t// A numeric x axis spreads the values evenly between its ends, as Mermaid does.\n\t\tcategories = Array.from({ length: longest }, (_, i) =>\n\t\t\txMin !== null && xMax !== null ? String(Math.round((xMin + ((xMax - xMin) * i) / Math.max(1, longest - 1)) * 100) / 100) : String(i + 1)\n\t\t);\n\t}\n\tconst bars = graph.nodes.some((n) => n.shape === 'bar');\n\treturn createChartSpec({\n\t\ttype: bars ? 'bar' : 'line',\n\t\ttitle: graph.title ?? '',\n\t\tcategories,\n\t\tseries: graph.nodes.map((n) => ({ id: n.id, name: n.label, values: numbers(n.data?.values), type: n.shape === 'line' ? 'line' : 'bar' })),\n\t\torientation: data.horizontal ? 'horizontal' : 'vertical',\n\t\txAxis: { show: true, title: text(data.xTitle), min: null, max: null, step: null, gridlines: false, format: {} },\n\t\tyAxis: { show: true, title: text(data.yTitle), min: num(data.yMin), max: num(data.yMax), step: null, gridlines: true, format: {} },\n\t\tlegend: { show: graph.nodes.some((n) => n.data?.titled), position: 'top' },\n\t\t...(palette ? { palette: [...palette] } : {})\n\t});\n}\n\n/** The chart spec a `radar-beta` graph describes. */\nexport function radarChartSpec(graph: DiagramGraph, palette?: readonly string[]): ChartSpec {\n\tconst data = graph.data ?? {};\n\treturn createChartSpec({\n\t\ttype: 'radar',\n\t\ttitle: graph.title ?? '',\n\t\tcategories: Array.isArray(data.axes) ? data.axes.map(String) : [],\n\t\tseries: graph.nodes.map((n) => ({ id: n.id, name: n.label, values: numbers(n.data?.values) })),\n\t\tyAxis: { show: true, title: '', min: num(data.min), max: num(data.max), step: null, gridlines: true, format: {} },\n\t\tlegend: { show: data.showLegend !== false, position: 'top' },\n\t\t...(palette ? { palette: [...palette] } : {})\n\t});\n}\n\nfunction targetOf(target: ChartSelection | undefined): string | undefined {\n\tif (!target) return undefined;\n\tif (target.kind === 'series' || target.kind === 'point') return target.seriesId;\n\tif (target.kind === 'title') return DIAGRAM_TITLE_TARGET;\n\treturn undefined;\n}\n\n/** Chart marks as diagram marks, moved to `(x, y)`. */\nfunction toDiagramMarks(marks: readonly ChartMark[], x: number, y: number): DiagramMark[] {\n\treturn marks\n\t\t.filter((m) => m.role !== 'selection' && m.role !== 'drag-readout')\n\t\t.map((m): DiagramMark => {\n\t\t\tconst base = { key: m.key, role: m.role, ...(targetOf(m.target) ? { target: targetOf(m.target) } : {}), ...(m.opacity !== undefined ? { opacity: m.opacity } : {}) };\n\t\t\tswitch (m.kind) {\n\t\t\t\tcase 'rect':\n\t\t\t\t\treturn { ...base, kind: 'rect', x: m.x + x, y: m.y + y, width: m.width, height: m.height, fill: m.fill, stroke: m.stroke, strokeWidth: m.strokeWidth, radius: m.radius };\n\t\t\t\tcase 'circle':\n\t\t\t\t\treturn { ...base, kind: 'circle', cx: m.cx + x, cy: m.cy + y, r: m.r, fill: m.fill, stroke: m.stroke, strokeWidth: m.strokeWidth };\n\t\t\t\tcase 'line':\n\t\t\t\t\treturn { ...base, kind: 'line', x1: m.x1 + x, y1: m.y1 + y, x2: m.x2 + x, y2: m.y2 + y, stroke: m.stroke, strokeWidth: m.strokeWidth, dash: m.dash };\n\t\t\t\tcase 'text':\n\t\t\t\t\treturn { ...base, kind: 'text', x: m.x + x, y: m.y + y, text: m.text, anchor: m.anchor, baseline: m.baseline, size: m.size, weight: m.weight, fill: m.fill, rotate: m.rotate };\n\t\t\t\tcase 'path':\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...base,\n\t\t\t\t\t\tkind: 'path',\n\t\t\t\t\t\t// Chart paths are absolute, so the whole path moves with a translate prefix.\n\t\t\t\t\t\td: x || y ? translatePath(m.d, x, y) : m.d,\n\t\t\t\t\t\tfill: m.fill,\n\t\t\t\t\t\tstroke: m.stroke,\n\t\t\t\t\t\tstrokeWidth: m.strokeWidth,\n\t\t\t\t\t\tdash: m.dash\n\t\t\t\t\t};\n\t\t\t}\n\t\t});\n}\n\n/** Moves an absolute M / L / C / Q / Z path (what the chart layout draws) by (dx, dy). */\nfunction translatePath(d: string, dx: number, dy: number): string {\n\tlet i = 0;\n\treturn d.replace(/-?\\d*\\.?\\d+(?:e-?\\d+)?/gi, (v) => String(Math.round((Number(v) + (i++ % 2 === 0 ? dx : dy)) * 100) / 100));\n}\n\nexport const xyChartScene: FamilyScene = ({ graph, theme }) => {\n\tconst box = XY_CHART_BOX;\n\tconst scene = layoutChart(xyChartSpec(graph, theme.palette), { width: box.width, height: box.height, theme: chartTheme(theme) });\n\treturn toDiagramMarks(scene.marks, box.x, box.y);\n};\n\nexport const radarScene: FamilyScene = ({ graph, theme }) => {\n\tconst box = RADAR_CHART_BOX;\n\tconst scene = layoutChart(radarChartSpec(graph, theme.palette), { width: box.width, height: box.height, theme: chartTheme(theme) });\n\treturn toDiagramMarks(scene.marks, box.x, box.y);\n};\n","// Follows Ursula's src/react/canvases/sequence.tsx (and its .ursula-seq__* styles).\nimport { SEQUENCE_METRICS, sequenceGeometry } from '../../../diagram/sequence/geometry.js';\nimport type { ArrowHead, EdgePattern, GraphEdge } from '../../../diagram/types.js';\nimport { DIAGRAM_TITLE_TARGET, estimateTextWidth, sceneColor, type DiagramMark, type DiagramSceneTheme, type FamilyScene } from '../scene-kit.js';\n\nconst M = SEQUENCE_METRICS;\n/** Participant, note and message text (Ursula caps them at 14px). */\nconst LABEL_SIZE = 14;\nconst LINE_HEIGHT = 1.25;\n/** Fragment tabs, fragment conditions. */\nconst SMALL_SIZE = 13;\nconst TITLE_SIZE = 18;\n/** Palette slot of Mermaid's yellow note colour. */\nconst NOTE_COLOR = 5;\n\n/** Label text as Ursula shows it (`displayLabel`): backticks dropped, `<br>` as new lines, entities decoded. */\nfunction displayLines(label: string): string[] {\n\tlet text = label;\n\tif (text.startsWith('`') && text.endsWith('`')) text = text.slice(1, -1);\n\treturn text\n\t\t.replace(/<br\\s*\\/?>/gi, '\\n')\n\t\t.replace(/#quot;/g, '\"')\n\t\t.replace(/#(\\d+);/g, (_, n: string) => String.fromCharCode(Number(n)))\n\t\t.split('\\n');\n}\n\nfunction dashFor(pattern: EdgePattern): string | undefined {\n\treturn pattern === 'dotted' ? '6 4' : pattern === 'invisible' ? '0 1' : undefined;\n}\n\n/** Arrow heads the mark contract draws: `->>` filled, `-)` open; crosses are drawn separately. */\nfunction markerFor(head: ArrowHead): 'arrow' | 'arrowclosed' | undefined {\n\tif (head === 'none' || head === 'arrow_cross') return undefined;\n\treturn head === 'arrow_open' ? 'arrow' : 'arrowclosed';\n}\n\n/** Lines of text centred on (x, cy), one mark per line. */\nfunction textLines(\n\tlines: string[],\n\tbase: { key: string; role: string; target?: string; x: number; cy: number; size: number; fill: string; weight?: number; anchor?: 'start' | 'middle' | 'end' }\n): DiagramMark[] {\n\tconst lh = base.size * LINE_HEIGHT;\n\tconst top = base.cy - ((lines.length - 1) * lh) / 2;\n\treturn lines.map((text, i) => ({\n\t\tkind: 'text',\n\t\tkey: lines.length === 1 ? base.key : `${base.key}:${i}`,\n\t\trole: base.role,\n\t\t...(base.target ? { target: base.target } : {}),\n\t\tx: base.x,\n\t\ty: top + i * lh,\n\t\ttext,\n\t\tanchor: base.anchor ?? 'middle',\n\t\tbaseline: 'middle',\n\t\tsize: base.size,\n\t\tfill: base.fill,\n\t\t...(base.weight ? { weight: base.weight } : {})\n\t}));\n}\n\n/** A participant box (or stick figure for `actor`) with its name; `mirror` is the copy under the lifelines. */\nfunction participantMarks(\n\tnode: { id: string; label: string; shape: string },\n\tbox: { x: number; y: number; width: number; height: number },\n\tmirror: boolean,\n\ttheme: DiagramSceneTheme\n): DiagramMark[] {\n\tconst prefix = mirror ? 'mirror' : 'participant';\n\tconst role = mirror ? 'participant-mirror' : 'participant';\n\tconst lines = displayLines(node.label || node.id);\n\tif (node.shape !== 'actor') {\n\t\treturn [\n\t\t\t{ kind: 'rect', key: `${prefix}:${node.id}`, role, target: node.id, ...box, fill: theme.nodeFill, stroke: theme.nodeStroke, strokeWidth: 1, radius: 3 },\n\t\t\t...textLines(lines, { key: `${prefix}-label:${node.id}`, role: `${role}-label`, target: node.id, x: box.x + box.width / 2, cy: box.y + box.height / 2, size: LABEL_SIZE, fill: theme.text })\n\t\t];\n\t}\n\t// Actor: a 28×34 figure (viewBox 40×46, scaled to fit) over the name, bottom-aligned with a 2px gap.\n\tconst lh = LABEL_SIZE * LINE_HEIGHT;\n\tconst labelHeight = lines.length * lh;\n\tconst scale = Math.min(28 / 40, 34 / 46);\n\tconst figTop = box.y + box.height - labelHeight - 2 - 34 + (34 - 46 * scale) / 2;\n\tconst figLeft = box.x + box.width / 2 - 20 * scale;\n\tconst fx = (v: number) => figLeft + v * scale;\n\tconst fy = (v: number) => figTop + v * scale;\n\tconst d = `M${fx(20)},${fy(15)} V${fy(31)} M${fx(6)},${fy(21)} H${fx(34)} M${fx(20)},${fy(31)} L${fx(9)},${fy(44)} M${fx(20)},${fy(31)} L${fx(31)},${fy(44)}`;\n\treturn [\n\t\t{ kind: 'circle', key: `${prefix}-head:${node.id}`, role: `${role}-figure`, target: node.id, cx: fx(20), cy: fy(8), r: 7 * scale, fill: theme.nodeFill, stroke: theme.nodeStroke, strokeWidth: 1.5 },\n\t\t{\n\t\t\tkind: 'path',\n\t\t\tkey: `${prefix}-body:${node.id}`,\n\t\t\trole: `${role}-figure`,\n\t\t\ttarget: node.id,\n\t\t\td,\n\t\t\tfill: 'none',\n\t\t\tstroke: theme.nodeStroke,\n\t\t\tstrokeWidth: 1.5,\n\t\t\tbox: { x: fx(6), y: fy(15), width: 28 * scale, height: 29 * scale }\n\t\t},\n\t\t...textLines(lines, {\n\t\t\tkey: `${prefix}-label:${node.id}`,\n\t\t\trole: `${role}-label`,\n\t\t\ttarget: node.id,\n\t\t\tx: box.x + box.width / 2,\n\t\t\tcy: box.y + box.height - labelHeight / 2,\n\t\t\tsize: LABEL_SIZE,\n\t\t\tfill: theme.text\n\t\t})\n\t];\n}\n\n/** Mermaid's `x` head: a cross at the end of a message heading in direction `dir` (±1). */\nfunction crossMark(key: string, target: string, x: number, y: number, dir: number, stroke: string): DiagramMark {\n\tconst cx = x - dir * 2.8;\n\tconst h = 2.8;\n\treturn {\n\t\tkind: 'path',\n\t\tkey,\n\t\trole: 'message-head',\n\t\ttarget,\n\t\td: `M${cx - h},${y - h} L${cx + h},${y + h} M${cx + h},${y - h} L${cx - h},${y + h}`,\n\t\tstroke,\n\t\tstrokeWidth: 1.5\n\t};\n}\n\n/**\n * Sequence diagrams as Mermaid draws them (Ursula's `canvases/sequence.tsx`): participants in\n * a row, mirrored under the lifelines, one horizontal message per row with its label and\n * (with `autonumber`) a numbered dot, notes, `alt`/`loop` fragment frames with their\n * dividers, activation bars and coloured boxes. Participants, their lifelines and mirrors\n * select the participant; a message's line, head, number and label select the message; a\n * box's frame selects its group.\n */\nexport const sequenceScene: FamilyScene = ({ graph, theme }) => {\n\tconst geo = sequenceGeometry(graph);\n\tconst marks: DiagramMark[] = [];\n\n\t// Boxes (groups), behind everything.\n\tfor (const group of graph.groups) {\n\t\tconst box = geo.boxes.get(group.id);\n\t\tif (!box) continue;\n\t\tconst color = typeof group.data?.color === 'string' ? group.data.color : undefined;\n\t\tmarks.push(\n\t\t\t{ kind: 'rect', key: `box:${group.id}`, role: 'box', target: group.id, ...box, fill: color ?? 'none', stroke: theme.groupStroke, strokeWidth: 1 },\n\t\t\t...textLines(displayLines(group.label), {\n\t\t\t\tkey: `box-label:${group.id}`,\n\t\t\t\trole: 'box-label',\n\t\t\t\ttarget: group.id,\n\t\t\t\tx: box.x + box.width / 2,\n\t\t\t\tcy: box.y + 4 + (LABEL_SIZE * LINE_HEIGHT) / 2,\n\t\t\t\tsize: LABEL_SIZE,\n\t\t\t\tweight: 600,\n\t\t\t\tfill: theme.groupText\n\t\t\t})\n\t\t);\n\t}\n\n\t// Title, centred over the participants and boxes (Ursula's title node: 56px above them).\n\tif (graph.title) {\n\t\tconst lefts = [...[...geo.columns.values()].map((c) => c.x), ...[...geo.boxes.values()].map((b) => b.x)];\n\t\tconst rights = [...[...geo.columns.values()].map((c) => c.x + c.width), ...[...geo.boxes.values()].map((b) => b.x + b.width)];\n\t\tconst tops = [0, ...[...geo.boxes.values()].map((b) => b.y)];\n\t\tconst minX = lefts.length ? Math.min(...lefts) : 0;\n\t\tconst maxX = rights.length ? Math.max(...rights) : 240;\n\t\tmarks.push({ kind: 'text', key: 'title', role: 'title', target: DIAGRAM_TITLE_TARGET, x: (minX + maxX) / 2, y: Math.min(...tops) - 38, text: graph.title, anchor: 'middle', baseline: 'middle', size: TITLE_SIZE, weight: 600, fill: theme.text });\n\t}\n\n\t// Fragments (`alt`, `loop`…): frame, keyword tab, condition and dashed dividers; outer ones first.\n\t[...geo.fragments]\n\t\t.map((f, i) => ({ f, i }))\n\t\t.sort((a, b) => a.f.depth - b.f.depth || a.i - b.i)\n\t\t.forEach(({ f, i }) => {\n\t\t\tmarks.push({ kind: 'rect', key: `fragment:${i}`, role: 'fragment', x: f.x, y: f.y, width: f.width, height: f.height, fill: 'none', stroke: theme.mutedText, strokeWidth: 1.5 });\n\t\t\tconst tabW = estimateTextWidth(f.keyword, SMALL_SIZE) + 22;\n\t\t\tconst tabH = SMALL_SIZE * LINE_HEIGHT + 4;\n\t\t\tmarks.push(\n\t\t\t\t{\n\t\t\t\t\tkind: 'path',\n\t\t\t\t\tkey: `fragment-tab:${i}`,\n\t\t\t\t\trole: 'fragment-tab',\n\t\t\t\t\td: `M${f.x},${f.y} H${f.x + tabW} V${f.y + tabH * 0.6} L${f.x + tabW - 8},${f.y + tabH} H${f.x} Z`,\n\t\t\t\t\tfill: theme.groupFill,\n\t\t\t\t\tstroke: theme.mutedText,\n\t\t\t\t\tstrokeWidth: 1.5\n\t\t\t\t},\n\t\t\t\t{ kind: 'text', key: `fragment-keyword:${i}`, role: 'fragment-keyword', x: f.x + 8, y: f.y + tabH / 2, text: f.keyword, anchor: 'start', baseline: 'middle', size: SMALL_SIZE, weight: 600, fill: theme.groupText }\n\t\t\t);\n\t\t\tif (f.text)\n\t\t\t\tmarks.push({ kind: 'text', key: `fragment-text:${i}`, role: 'fragment-text', x: f.x + f.width / 2, y: f.y + 3, text: `[${f.text}]`, anchor: 'middle', baseline: 'top', size: SMALL_SIZE, weight: 600, fill: theme.text });\n\t\t\tf.dividers.forEach((d, j) => {\n\t\t\t\tmarks.push({ kind: 'line', key: `fragment-divider:${i}:${j}`, role: 'fragment-divider', x1: f.x, y1: d.y, x2: f.x + f.width, y2: d.y, stroke: theme.mutedText, strokeWidth: 1.5, dash: '6 4' });\n\t\t\t\tif (d.text)\n\t\t\t\t\tmarks.push({ kind: 'text', key: `fragment-divider-text:${i}:${j}`, role: 'fragment-text', x: f.x + f.width / 2, y: d.y + 3, text: `[${d.text}]`, anchor: 'middle', baseline: 'top', size: SMALL_SIZE, weight: 600, fill: theme.text });\n\t\t\t});\n\t\t});\n\n\t// Lifelines.\n\tfor (const col of geo.columns.values())\n\t\tmarks.push({ kind: 'line', key: `lifeline:${col.id}`, role: 'lifeline', target: col.id, x1: col.center, y1: geo.lifelineTop, x2: col.center, y2: geo.lifelineBottom, stroke: theme.grid, strokeWidth: 1 });\n\n\t// Activation bars.\n\tgeo.activations.forEach((a, i) =>\n\t\tmarks.push({ kind: 'rect', key: `activation:${i}`, role: 'activation', x: a.x, y: a.top, width: M.activationWidth, height: Math.max(8, a.bottom - a.top), fill: theme.groupFill, stroke: theme.edge, strokeWidth: 1 })\n\t);\n\n\t// Participants, top and mirrored at the bottom.\n\tfor (const node of graph.nodes) {\n\t\tconst col = geo.columns.get(node.id);\n\t\tif (!col) continue;\n\t\tmarks.push(...participantMarks(node, { x: col.x, y: 0, width: col.width, height: M.actorHeight }, false, theme));\n\t\tmarks.push(...participantMarks(node, { x: col.x, y: geo.lifelineBottom, width: col.width, height: M.actorHeight }, true, theme));\n\t}\n\n\t// Notes: a tinted box (fill and outline drawn apart so the tint does not fade the border).\n\tconst noteColor = sceneColor(theme, NOTE_COLOR);\n\tgeo.notes.forEach((n, i) => {\n\t\tconst box = { x: n.x, y: n.y, width: n.width, height: n.height };\n\t\tmarks.push(\n\t\t\t{ kind: 'rect', key: `note-bg:${i}`, role: 'note', ...box, fill: theme.background },\n\t\t\t{ kind: 'rect', key: `note-fill:${i}`, role: 'note', ...box, fill: noteColor, opacity: 0.3 },\n\t\t\t{ kind: 'rect', key: `note:${i}`, role: 'note', ...box, fill: 'none', stroke: noteColor, strokeWidth: 1 },\n\t\t\t...textLines(displayLines(n.text), { key: `note-text:${i}`, role: 'note-text', x: n.x + n.width / 2, cy: n.y + n.height / 2, size: LABEL_SIZE, fill: theme.text })\n\t\t);\n\t});\n\n\t// Messages, above everything else.\n\tconst edges = new Map<string, GraphEdge>(graph.edges.map((e) => [e.id, e]));\n\tconst autonumber = !!graph.data?.autonumber;\n\tgeo.messages.forEach((row, index) => {\n\t\tconst edge = edges.get(row.edgeId);\n\t\tif (!edge) return;\n\t\tconst x1 = geo.columns.get(edge.source)?.center ?? 0;\n\t\tconst x2 = geo.columns.get(edge.target)?.center ?? 0;\n\t\tconst y = row.y;\n\t\tconst stroke = theme.edge;\n\t\tconst strokeWidth = edge.pattern === 'thick' ? 3.5 : 1.5;\n\t\tconst dash = dashFor(edge.pattern);\n\t\tconst markerEnd = markerFor(edge.arrowEnd);\n\t\tconst markerStart = markerFor(edge.arrowStart);\n\t\tconst heads = { ...(markerEnd ? { markerEnd } : {}), ...(markerStart ? { markerStart } : {}) };\n\t\tlet endX: number;\n\t\tlet endDir: number;\n\t\tlet startDir: number;\n\t\tlet endY = y;\n\t\tlet startY = y;\n\t\tif (row.self) {\n\t\t\tendX = x1 + 4;\n\t\t\tendDir = -1;\n\t\t\tstartDir = -1;\n\t\t\tendY = y + 10;\n\t\t\tstartY = y - 10;\n\t\t\tmarks.push({\n\t\t\t\tkind: 'path',\n\t\t\t\tkey: `message:${edge.id}`,\n\t\t\t\trole: 'message',\n\t\t\t\ttarget: edge.id,\n\t\t\t\td: `M${x1},${y - 10} H${x1 + 44} V${y + 10} H${x1 + 4}`,\n\t\t\t\tfill: 'none',\n\t\t\t\tstroke,\n\t\t\t\tstrokeWidth,\n\t\t\t\t...(dash ? { dash } : {}),\n\t\t\t\t...heads,\n\t\t\t\tbox: { x: x1, y: y - 10, width: 44, height: 20 }\n\t\t\t});\n\t\t} else {\n\t\t\tendX = x2 + (x2 > x1 ? -2 : 2);\n\t\t\tendDir = x2 >= x1 ? 1 : -1;\n\t\t\tstartDir = -endDir;\n\t\t\tmarks.push({ kind: 'line', key: `message:${edge.id}`, role: 'message', target: edge.id, x1, y1: y, x2: endX, y2: y, stroke, strokeWidth, ...(dash ? { dash } : {}), ...heads });\n\t\t}\n\t\tif (edge.arrowEnd === 'arrow_cross') marks.push(crossMark(`message-cross-end:${edge.id}`, edge.id, endX, endY, endDir, stroke));\n\t\tif (edge.arrowStart === 'arrow_cross') marks.push(crossMark(`message-cross-start:${edge.id}`, edge.id, x1, startY, startDir, stroke));\n\t\tif (autonumber)\n\t\t\tmarks.push(\n\t\t\t\t{ kind: 'circle', key: `number-dot:${edge.id}`, role: 'message-number', target: edge.id, cx: x1, cy: y, r: 10, fill: stroke },\n\t\t\t\t{ kind: 'text', key: `number:${edge.id}`, role: 'message-number', target: edge.id, x: x1, y, text: String(index + 1), anchor: 'middle', baseline: 'middle', size: 11, weight: 600, fill: theme.background }\n\t\t\t);\n\t\tconst label = edge.label ? displayLines(edge.label).join(' ') : '';\n\t\tif (label)\n\t\t\tmarks.push({\n\t\t\t\tkind: 'text',\n\t\t\t\tkey: `message-label:${edge.id}`,\n\t\t\t\trole: 'message-label',\n\t\t\t\ttarget: edge.id,\n\t\t\t\tx: row.self ? x1 + 52 + 4 : (x1 + x2) / 2,\n\t\t\t\ty: row.self ? y - 18 : y - 13,\n\t\t\t\ttext: label,\n\t\t\t\tanchor: row.self ? 'start' : 'middle',\n\t\t\t\tbaseline: 'middle',\n\t\t\t\tsize: LABEL_SIZE,\n\t\t\t\tfill: theme.text\n\t\t\t});\n\t});\n\treturn marks;\n};\n","// Follows Ursula's src/react/canvases/journey.tsx.\nimport { JOURNEY_METRICS, journeyGeometry } from '../../../diagram/journey/geometry.js';\nimport { DIAGRAM_TITLE_TARGET, estimateTextWidth, sceneColor, type DiagramMark, type DiagramSceneTheme, type FamilyScene } from '../scene-kit.js';\n\nconst M = JOURNEY_METRICS;\n/** Mermaid's journey title is bold 4ex. */\nconst TITLE_SIZE = 32;\n\n/** Actor colours follow the section colours in the palette, so a dot reads on its task. */\nfunction actorColor(theme: DiagramSceneTheme, slot: number): string {\n\treturn sceneColor(theme, M.sectionColours + slot);\n}\n\n/** Splits `text` (explicit `<br>` / newlines first) into lines no wider than `max` px. */\nfunction wrapLines(text: string, size: number, max: number): string[] {\n\tconst lines: string[] = [];\n\tfor (const para of text.replace(/<br\\s*\\/?>/gi, '\\n').split('\\n')) {\n\t\tlet line = '';\n\t\tfor (const word of para.split(/\\s+/).filter(Boolean)) {\n\t\t\tconst next = line ? `${line} ${word}` : word;\n\t\t\tif (line && estimateTextWidth(next, size) > max) {\n\t\t\t\tlines.push(line);\n\t\t\t\tline = word;\n\t\t\t} else line = next;\n\t\t}\n\t\tlines.push(line);\n\t}\n\treturn lines;\n}\n\n/** Centred, wrapped label marks inside a box (smaller text when it needs more than two lines). */\nfunction boxLabel(\n\tkey: string,\n\trole: string,\n\ttarget: string,\n\ttext: string,\n\tbox: { x: number; y: number; width: number; height: number },\n\tfill: string\n): DiagramMark[] {\n\tlet size: number = M.fontSize;\n\tlet lines = wrapLines(text, size, box.width - 10);\n\tif (lines.length > 2) {\n\t\tsize = 13;\n\t\tlines = wrapLines(text, size, box.width - 8);\n\t}\n\tconst lineHeight = size * 1.15;\n\tconst max = Math.max(1, Math.floor(box.height / lineHeight));\n\tif (lines.length > max) lines = [...lines.slice(0, max - 1), `${lines[max - 1]}…`];\n\tconst top = box.y + box.height / 2 - (lines.length * lineHeight) / 2 + lineHeight / 2;\n\treturn lines.map((line, i) => ({\n\t\tkind: 'text',\n\t\tkey: `${key}:${i}`,\n\t\trole,\n\t\ttarget,\n\t\tx: box.x + box.width / 2,\n\t\ty: top + i * lineHeight,\n\t\ttext: line,\n\t\tanchor: 'middle',\n\t\tbaseline: 'middle',\n\t\tsize,\n\t\tfill\n\t}));\n}\n\n/**\n * User journeys as Mermaid draws them (Ursula's `canvases/journey.tsx`): the title, a dashed\n * line down from every task to its face (higher score = higher face), the thick axis, the\n * actor legend on the left, section bars on top and the task boxes with their actor dots.\n * Tasks, their dots and faces select the task; a section bar selects its section.\n */\nexport const journeyScene: FamilyScene = ({ graph, theme }) => {\n\tconst geo = journeyGeometry(graph);\n\tconst marks: DiagramMark[] = [];\n\tconst ink = theme.mutedText;\n\n\tif (graph.title)\n\t\tmarks.push({\n\t\t\tkind: 'text',\n\t\t\tkey: 'title',\n\t\t\trole: 'title',\n\t\t\ttarget: DIAGRAM_TITLE_TARGET,\n\t\t\tx: geo.title.x,\n\t\t\ty: geo.title.baseline + 7,\n\t\t\ttext: graph.title,\n\t\t\tanchor: 'start',\n\t\t\tbaseline: 'bottom',\n\t\t\tsize: TITLE_SIZE,\n\t\t\tweight: 700,\n\t\t\tfill: theme.text\n\t\t});\n\n\t// Mermaid draws the dashed line from the task's top to y = 450; the box covers its top.\n\tfor (const t of geo.tasks.values())\n\t\tmarks.push({ kind: 'line', key: `task-line:${t.id}`, role: 'task-line', x1: t.face.x, y1: t.y, x2: t.face.x, y2: M.lineBottom, stroke: theme.grid, strokeWidth: 1, dash: '4 2' });\n\n\tmarks.push({\n\t\tkind: 'line',\n\t\tkey: 'axis',\n\t\trole: 'axis',\n\t\tx1: geo.axis.x1,\n\t\ty1: geo.axis.y,\n\t\tx2: geo.axis.x2,\n\t\ty2: geo.axis.y,\n\t\tstroke: theme.edge,\n\t\tstrokeWidth: M.axisWidth,\n\t\tmarkerEnd: 'arrowclosed'\n\t});\n\n\tfor (const l of geo.legend)\n\t\tmarks.push(\n\t\t\t{ kind: 'circle', key: `legend-dot:${l.name}`, role: 'legend', cx: l.x, cy: l.y, r: M.actorRadius, fill: actorColor(theme, l.colour), stroke: theme.nodeStroke, strokeWidth: 1 },\n\t\t\t{ kind: 'text', key: `legend:${l.name}`, role: 'legend', x: l.labelX, y: l.y, text: l.name, anchor: 'start', baseline: 'middle', size: M.fontSize, fill: theme.text }\n\t\t);\n\n\t// Faces (Mermaid's `drawFace`): r = 15, eyes at ±r/3, mouth by score.\n\tfor (const t of geo.tasks.values()) {\n\t\tconst { x, y } = t.face;\n\t\tconst r = M.faceRadius;\n\t\tmarks.push(\n\t\t\t{ kind: 'circle', key: `face:${t.id}`, role: 'face', target: t.id, cx: x, cy: y, r, fill: theme.nodeFill, stroke: ink, strokeWidth: 2 },\n\t\t\t{ kind: 'circle', key: `eye-l:${t.id}`, role: 'face-eye', cx: x - r / 3, cy: y - r / 3, r: 1.5, fill: ink, stroke: ink, strokeWidth: 2 },\n\t\t\t{ kind: 'circle', key: `eye-r:${t.id}`, role: 'face-eye', cx: x + r / 3, cy: y - r / 3, r: 1.5, fill: ink, stroke: ink, strokeWidth: 2 }\n\t\t);\n\t\tif (t.score > 3)\n\t\t\t// Lower half circle centred 2px below the middle.\n\t\t\tmarks.push({ kind: 'path', key: `mouth:${t.id}`, role: 'face-mouth', d: `M${x + 7},${y + 2} A7,7 0 0 1 ${x - 7},${y + 2}`, fill: 'none', stroke: ink, strokeWidth: 1, box: { x: x - 7, y: y + 2, width: 14, height: 7 } });\n\t\telse if (t.score < 3)\n\t\t\t// Upper half circle centred 7px below the middle.\n\t\t\tmarks.push({ kind: 'path', key: `mouth:${t.id}`, role: 'face-mouth', d: `M${x - 7},${y + 7} A7,7 0 0 1 ${x + 7},${y + 7}`, fill: 'none', stroke: ink, strokeWidth: 1, box: { x: x - 7, y, width: 14, height: 7 } });\n\t\telse marks.push({ kind: 'line', key: `mouth:${t.id}`, role: 'face-mouth', x1: x - 5, y1: y + 7, x2: x + 5, y2: y + 7, stroke: ink, strokeWidth: 1 });\n\t}\n\n\tfor (const group of graph.groups) {\n\t\tconst s = geo.sections.get(group.id);\n\t\tif (!s) continue;\n\t\tmarks.push({\n\t\t\tkind: 'rect',\n\t\t\tkey: `section:${s.id}`,\n\t\t\trole: 'section',\n\t\t\ttarget: s.id,\n\t\t\tx: s.x,\n\t\t\ty: s.y,\n\t\t\twidth: s.width,\n\t\t\theight: s.height,\n\t\t\tfill: sceneColor(theme, s.colour),\n\t\t\tstroke: theme.nodeStroke,\n\t\t\tstrokeWidth: 1,\n\t\t\tradius: 3,\n\t\t\t// Mermaid draws no bar for an empty section; it is parked after the tasks, faded.\n\t\t\t...(s.empty ? { dash: '4 3', opacity: 0.6 } : {})\n\t\t});\n\t\tmarks.push(...boxLabel(`section-label:${s.id}`, 'section-label', s.id, group.label, s, theme.paletteText).map((m) => (s.empty ? { ...m, opacity: 0.6 } : m)));\n\t}\n\n\tfor (const node of graph.nodes) {\n\t\tconst t = geo.tasks.get(node.id);\n\t\tif (!t) continue;\n\t\tmarks.push({\n\t\t\tkind: 'rect',\n\t\t\tkey: `task:${t.id}`,\n\t\t\trole: 'task',\n\t\t\ttarget: t.id,\n\t\t\tx: t.x,\n\t\t\ty: t.y,\n\t\t\twidth: t.width,\n\t\t\theight: t.height,\n\t\t\tfill: sceneColor(theme, t.colour),\n\t\t\tstroke: theme.nodeStroke,\n\t\t\tstrokeWidth: 1,\n\t\t\tradius: 3\n\t\t});\n\t\tmarks.push(...boxLabel(`task-label:${t.id}`, 'task-label', t.id, node.label ?? node.id, t, theme.paletteText));\n\t\tfor (const a of t.actors)\n\t\t\tmarks.push({ kind: 'circle', key: `actor:${t.id}:${a.name}`, role: 'actor', target: t.id, cx: a.x, cy: a.y, r: M.actorRadius, fill: actorColor(theme, a.colour), stroke: theme.nodeStroke, strokeWidth: 1 });\n\t}\n\treturn marks;\n};\n","// Follows Ursula's src/react/canvases/quadrant.tsx.\nimport { QUADRANT_METRICS, quadrantGeometry } from '../../../diagram/quadrant/geometry.js';\nimport { DIAGRAM_TITLE_TARGET, sceneColor, type DiagramMark, type FamilyScene } from '../scene-kit.js';\n\nconst M = QUADRANT_METRICS;\n/** Quadrant tint over the background (Mermaid's quadrant fills are light shades). */\nconst QUADRANT_TINT = 0.18;\n\n/**\n * Quadrant charts as Mermaid draws them (Ursula's `canvases/quadrant.tsx`): the chart area\n * split into four tinted quadrants with their labels, the internal and external borders, the\n * x-axis labels under the chart (above it while there are no points), the y-axis labels\n * rotated on the left, the title on top, and every point a filled circle with its label just\n * below. A quadrant (its area or label) selects its group; a point or its label selects the\n * point. Points keep their own `color`, `strokeColor` and `strokeWidth`.\n */\nexport const quadrantScene: FamilyScene = ({ graph, theme }) => {\n\tconst geo = quadrantGeometry(graph);\n\tconst marks: DiagramMark[] = [];\n\tconst groups = new Map(graph.groups.map((g) => [g.id, g]));\n\n\tfor (const q of geo.quadrants) {\n\t\tconst group = groups.get(q.id);\n\t\tmarks.push({ kind: 'rect', key: `quadrant-fill:${q.id}`, role: 'quadrant-fill', x: q.x, y: q.y, width: q.width, height: q.height, fill: sceneColor(theme, q.n - 1), opacity: QUADRANT_TINT });\n\t\t// A transparent area on top of the tint carries the selection (the tint's opacity would hide its outline).\n\t\tmarks.push({ kind: 'rect', key: `quadrant:${q.id}`, role: 'quadrant', ...(group ? { target: q.id } : {}), x: q.x, y: q.y, width: q.width, height: q.height, fill: 'transparent' });\n\t}\n\tfor (const q of geo.quadrants) {\n\t\tconst group = groups.get(q.id);\n\t\t// Unnamed quadrants (`placeholder`) show no label, as in Mermaid.\n\t\tif (!group || group.data?.placeholder || !group.label) continue;\n\t\tmarks.push({\n\t\t\tkind: 'text',\n\t\t\tkey: `quadrant-label:${q.id}`,\n\t\t\trole: 'quadrant-label',\n\t\t\ttarget: q.id,\n\t\t\tx: q.labelX,\n\t\t\ty: q.labelY,\n\t\t\ttext: group.label,\n\t\t\tanchor: 'middle',\n\t\t\tbaseline: q.labelBaseline,\n\t\t\tsize: M.quadrantLabelFontSize,\n\t\t\tfill: theme.text\n\t\t});\n\t}\n\n\tgeo.borders.forEach((b, i) =>\n\t\tmarks.push({\n\t\t\tkind: 'line',\n\t\t\tkey: `border:${i}`,\n\t\t\trole: b.external ? 'border' : 'divider',\n\t\t\tx1: b.x1,\n\t\t\ty1: b.y1,\n\t\t\tx2: b.x2,\n\t\t\ty2: b.y2,\n\t\t\tstroke: theme.nodeStroke,\n\t\t\tstrokeWidth: b.external ? M.externalBorderWidth : M.internalBorderWidth\n\t\t})\n\t);\n\n\tfor (const l of geo.axisLabels)\n\t\tmarks.push({\n\t\t\tkind: 'text',\n\t\t\tkey: `axis:${l.key}`,\n\t\t\trole: 'axis-label',\n\t\t\tx: l.x,\n\t\t\ty: l.y,\n\t\t\ttext: l.text,\n\t\t\tanchor: l.anchor,\n\t\t\tbaseline: 'top',\n\t\t\tsize: l.rotation ? M.yAxisLabelFontSize : M.xAxisLabelFontSize,\n\t\t\tfill: theme.text,\n\t\t\t...(l.rotation ? { rotate: l.rotation } : {})\n\t\t});\n\n\tif (geo.title)\n\t\tmarks.push({ kind: 'text', key: 'title', role: 'title', target: DIAGRAM_TITLE_TARGET, x: geo.title.x, y: geo.title.y, text: geo.title.text, anchor: 'middle', baseline: 'top', size: M.titleFontSize, fill: theme.text });\n\n\tfor (const p of geo.points) {\n\t\tconst fill = p.color ?? sceneColor(theme, 0);\n\t\tconst strokeWidth = p.strokeWidth ? parseFloat(p.strokeWidth) : 0;\n\t\tmarks.push({\n\t\t\tkind: 'circle',\n\t\t\tkey: `point:${p.id}`,\n\t\t\trole: 'point',\n\t\t\ttarget: p.id,\n\t\t\tcx: p.cx,\n\t\t\tcy: p.cy,\n\t\t\tr: p.radius,\n\t\t\tfill,\n\t\t\t...(Number.isFinite(strokeWidth) && strokeWidth > 0 ? { stroke: p.strokeColor ?? fill, strokeWidth } : {})\n\t\t});\n\t\tmarks.push({\n\t\t\tkind: 'text',\n\t\t\tkey: `point-label:${p.id}`,\n\t\t\trole: 'point-label',\n\t\t\ttarget: p.id,\n\t\t\tx: p.cx,\n\t\t\ty: p.cy + M.pointTextPadding,\n\t\t\ttext: p.label.replace(/<br\\s*\\/?>/gi, ' '),\n\t\t\tanchor: 'middle',\n\t\t\tbaseline: 'top',\n\t\t\tsize: M.pointLabelFontSize,\n\t\t\tfill: theme.text\n\t\t});\n\t}\n\treturn marks;\n};\n","// Follows Ursula's src/react/canvases/gitgraph.tsx.\nimport { GIT_METRICS, estimateTextWidth, gitGraphGeometry, type GitCommitGeometry, type GitDirection } from '../../../diagram/gitgraph/geometry.js';\nimport { sceneColor, type DiagramMark, type DiagramSceneTheme, type FamilyScene } from '../scene-kit.js';\n\nconst M = GIT_METRICS;\n\ntype Pt = [number, number];\n\n/** Rotates (dx, dy) by `deg` degrees (SVG / CSS sense: positive is clockwise on screen). */\nfunction rot([dx, dy]: Pt, deg: number): Pt {\n\tconst a = (deg * Math.PI) / 180;\n\tconst c = Math.cos(a);\n\tconst s = Math.sin(a);\n\treturn [dx * c - dy * s, dx * s + dy * c];\n}\n\nconst polygon = (points: Pt[]) => `M${points.map(([x, y]) => `${round(x)},${round(y)}`).join(' L')} Z`;\nconst round = (v: number) => Math.round(v * 100) / 100;\n\nfunction boxOf(points: Pt[]): { x: number; y: number; width: number; height: number } {\n\tconst xs = points.map((p) => p[0]);\n\tconst ys = points.map((p) => p[1]);\n\tconst x = Math.min(...xs);\n\tconst y = Math.min(...ys);\n\treturn { x, y, width: Math.max(...xs) - x, height: Math.max(...ys) - y };\n}\n\n/**\n * Bounds of a parent link (`M`, `L` and quarter-circle `A` commands in absolute\n * coordinates); each arc stays inside the box of its end points.\n */\nfunction arrowBox(d: string): { x: number; y: number; width: number; height: number } {\n\tconst points: Pt[] = [];\n\tfor (const [, cmd, args] of d.matchAll(/([MLA])([^MLA]*)/g)) {\n\t\tconst nums = (args!.match(/-?\\d+(?:\\.\\d+)?(?:e-?\\d+)?/gi) ?? []).map(Number);\n\t\tif (cmd === 'A') {\n\t\t\tfor (let i = 0; i + 6 < nums.length; i += 7) points.push([nums[i + 5]!, nums[i + 6]!]);\n\t\t} else for (let i = 0; i + 1 < nums.length; i += 2) points.push([nums[i]!, nums[i + 1]!]);\n\t}\n\treturn points.length ? boxOf(points) : { x: 0, y: 0, width: 0, height: 0 };\n}\n\n/** Mermaid's commit symbol per type (`drawCommitBullet`), centred on the commit. */\nfunction commitSymbol(c: GitCommitGeometry, theme: DiagramSceneTheme): DiagramMark[] {\n\tconst color = sceneColor(theme, c.color);\n\tconst base = { target: c.id };\n\tswitch (c.symbol) {\n\t\tcase 'HIGHLIGHT':\n\t\t\treturn [\n\t\t\t\t{ kind: 'rect', key: `commit:${c.id}`, role: 'commit', ...base, x: c.x - 10, y: c.y - 10, width: 20, height: 20, fill: theme.text, stroke: theme.text },\n\t\t\t\t{ kind: 'rect', key: `commit-inner:${c.id}`, role: 'commit-inner', ...base, x: c.x - 6, y: c.y - 6, width: 12, height: 12, fill: color, stroke: color }\n\t\t\t];\n\t\tcase 'CHERRY_PICK':\n\t\t\treturn [\n\t\t\t\t{ kind: 'circle', key: `commit:${c.id}`, role: 'commit', ...base, cx: c.x, cy: c.y, r: M.commitRadius, fill: theme.text },\n\t\t\t\t{ kind: 'circle', key: `cherry-l:${c.id}`, role: 'cherry', ...base, cx: c.x - 3, cy: c.y + 2, r: 2.75, fill: theme.background },\n\t\t\t\t{ kind: 'circle', key: `cherry-r:${c.id}`, role: 'cherry', ...base, cx: c.x + 3, cy: c.y + 2, r: 2.75, fill: theme.background },\n\t\t\t\t{\n\t\t\t\t\tkind: 'path',\n\t\t\t\t\tkey: `cherry-stem:${c.id}`,\n\t\t\t\t\trole: 'cherry-stem',\n\t\t\t\t\t...base,\n\t\t\t\t\td: `M${c.x + 3},${c.y + 1} L${c.x},${c.y - 5} M${c.x - 3},${c.y + 1} L${c.x},${c.y - 5}`,\n\t\t\t\t\tfill: 'none',\n\t\t\t\t\tstroke: theme.background\n\t\t\t\t}\n\t\t\t];\n\t\tdefault: {\n\t\t\tconst marks: DiagramMark[] = [{ kind: 'circle', key: `commit:${c.id}`, role: 'commit', ...base, cx: c.x, cy: c.y, r: M.commitRadius, fill: color, stroke: color }];\n\t\t\tif (c.symbol === 'MERGE')\n\t\t\t\tmarks.push({ kind: 'circle', key: `commit-inner:${c.id}`, role: 'commit-inner', ...base, cx: c.x, cy: c.y, r: 6, fill: theme.background, stroke: theme.background });\n\t\t\tif (c.symbol === 'REVERSE')\n\t\t\t\tmarks.push({\n\t\t\t\t\tkind: 'path',\n\t\t\t\t\tkey: `commit-reverse:${c.id}`,\n\t\t\t\t\trole: 'commit-reverse',\n\t\t\t\t\t...base,\n\t\t\t\t\td: `M${c.x - 5},${c.y - 5} L${c.x + 5},${c.y + 5} M${c.x - 5},${c.y + 5} L${c.x + 5},${c.y - 5}`,\n\t\t\t\t\tfill: 'none',\n\t\t\t\t\tstroke: theme.background,\n\t\t\t\t\tstrokeWidth: 3\n\t\t\t\t});\n\t\t\treturn marks;\n\t\t}\n\t}\n}\n\n/** The commit label (its id) on a faint background, rotated -45° about its right end like Mermaid. */\nfunction commitLabel(c: GitCommitGeometry, text: string, direction: GitDirection, theme: DiagramSceneTheme): DiagramMark[] {\n\tconst w = estimateTextWidth(text, M.labelFontSize);\n\tconst anchor: Pt = direction === 'LR' ? [1 - 0.026 * w, 18.1 - 0.0136 * w] : [-14.9, 9.2];\n\tconst ax = c.x + anchor[0];\n\tconst ay = c.y + anchor[1];\n\t// The label box (2px padding, 14px line) ends at the anchor; it turns about the anchor.\n\tconst corners = ([[-w - 4, -7], [0, -7], [0, 7], [-w - 4, 7]] as Pt[]).map((p) => {\n\t\tconst [dx, dy] = rot(p, -45);\n\t\treturn [ax + dx, ay + dy] as Pt;\n\t});\n\tconst [tx, ty] = rot([-2, 0], -45);\n\treturn [\n\t\t{ kind: 'path', key: `label-bg:${c.id}`, role: 'commit-label-bg', target: c.id, d: polygon(corners), fill: theme.edgeLabelBackground, opacity: 0.5 },\n\t\t{ kind: 'text', key: `label:${c.id}`, role: 'commit-label', target: c.id, x: ax + tx, y: ay + ty, text, anchor: 'end', baseline: 'middle', size: M.labelFontSize, fill: theme.text, rotate: -45 }\n\t];\n}\n\n/** A tag: a label with a pointed, punched end above the commit (`drawCommitTags`). */\nfunction commitTag(c: GitCommitGeometry, text: string, index: number, direction: GitDirection, theme: DiagramSceneTheme): DiagramMark[] {\n\tconst w = estimateTextWidth(text, M.labelFontSize);\n\tconst h2 = M.labelTextHeight / 2;\n\tconst key = `${c.id}:${index}`;\n\tlet points: Pt[];\n\tlet hole: Pt;\n\tlet label: Pt;\n\tlet rotate: number | undefined;\n\tif (direction === 'LR') {\n\t\tconst ly = -19.2 - index * 20;\n\t\tconst local: Pt[] = [\n\t\t\t[-w / 2 - 12, ly + 2],\n\t\t\t[-w / 2 - 12, ly - 2],\n\t\t\t[-w / 2 - 4, ly - h2 - 2],\n\t\t\t[w / 2 + 4, ly - h2 - 2],\n\t\t\t[w / 2 + 4, ly + h2 + 2],\n\t\t\t[-w / 2 - 4, ly + h2 + 2]\n\t\t];\n\t\tpoints = local.map(([x, y]) => [c.x + x, c.y + y]);\n\t\thole = [c.x - w / 2 - 8, c.y + ly];\n\t\tlabel = [c.x, c.y + ly];\n\t} else {\n\t\t// Vertical graphs: the tag hangs at 45° to the right of the commit.\n\t\tconst ox = c.x + 12;\n\t\tconst oy = c.y + 2 + index * 20;\n\t\tconst place = (p: Pt): Pt => {\n\t\t\tconst [dx, dy] = rot(p, 45);\n\t\t\treturn [ox + dx, oy + dy];\n\t\t};\n\t\tpoints = ([[0, 2], [0, -2], [10, -h2 - 2], [14 + w, -h2 - 2], [14 + w, h2 + 2], [10, h2 + 2]] as Pt[]).map(place);\n\t\thole = place([2, 0]);\n\t\tlabel = place([12 + w / 2, 0]);\n\t\trotate = 45;\n\t}\n\treturn [\n\t\t{ kind: 'path', key: `tag:${key}`, role: 'tag', target: c.id, d: polygon(points), fill: theme.groupFill, stroke: theme.groupStroke },\n\t\t{ kind: 'circle', key: `tag-hole:${key}`, role: 'tag-hole', target: c.id, cx: hole[0], cy: hole[1], r: 1.5, fill: theme.text },\n\t\t{\n\t\t\tkind: 'text',\n\t\t\tkey: `tag-label:${key}`,\n\t\t\trole: 'tag-label',\n\t\t\ttarget: c.id,\n\t\t\tx: label[0],\n\t\t\ty: label[1],\n\t\t\ttext,\n\t\t\tanchor: 'middle',\n\t\t\tbaseline: 'middle',\n\t\t\tsize: M.labelFontSize,\n\t\t\tfill: theme.text,\n\t\t\t...(rotate !== undefined ? { rotate } : {})\n\t\t}\n\t];\n}\n\n/**\n * Git graphs as Mermaid draws them (Ursula's `canvases/gitgraph.tsx`): a dashed lane per\n * branch with its name in a pill of the branch colour, Mermaid's thick lane-coloured parent\n * links with their arcs, commits as bullets along the lanes (normal, reverse, highlight,\n * merge and cherry-pick symbols), rotated commit labels and tags. A bullet, its label and\n * tags select the commit; a pill selects the branch; a link selects the parent edge.\n */\nexport const gitGraphScene: FamilyScene = ({ graph, theme }) => {\n\tconst geo = gitGraphGeometry(graph);\n\tconst marks: DiagramMark[] = [];\n\tfor (const lane of geo.lanes) {\n\t\tconst { x1, y1, x2, y2 } = lane.line;\n\t\tmarks.push({ kind: 'line', key: `lane:${lane.branch}`, role: 'lane', x1, y1, x2, y2, stroke: theme.grid, strokeWidth: 1, dash: '2 2' });\n\t}\n\tfor (const edge of graph.edges) {\n\t\tconst arrow = geo.arrows.get(edge.id);\n\t\tif (!arrow) continue;\n\t\tmarks.push({\n\t\t\tkind: 'path',\n\t\t\tkey: `arrow:${edge.id}`,\n\t\t\trole: 'parent-link',\n\t\t\ttarget: edge.id,\n\t\t\td: arrow.path,\n\t\t\tfill: 'none',\n\t\t\tstroke: sceneColor(theme, arrow.color),\n\t\t\tstrokeWidth: 8,\n\t\t\tbox: arrowBox(arrow.path)\n\t\t});\n\t}\n\tconst groups = new Map(graph.groups.map((g) => [g.id, g]));\n\tfor (const lane of geo.lanes) {\n\t\tconst group = groups.get(lane.branch);\n\t\tif (!group) continue;\n\t\tconst { x, y, width, height } = lane.label;\n\t\tconst color = sceneColor(theme, lane.color);\n\t\tmarks.push(\n\t\t\t{ kind: 'rect', key: `branch:${lane.branch}`, role: 'branch', target: lane.branch, x, y, width, height, fill: color, radius: 4 },\n\t\t\t{\n\t\t\t\tkind: 'text',\n\t\t\t\tkey: `branch-label:${lane.branch}`,\n\t\t\t\trole: 'branch-label',\n\t\t\t\ttarget: lane.branch,\n\t\t\t\tx: x + width / 2,\n\t\t\t\ty: y + height / 2,\n\t\t\t\ttext: group.label || group.id,\n\t\t\t\tanchor: 'middle',\n\t\t\t\tbaseline: 'middle',\n\t\t\t\tsize: M.branchFontSize,\n\t\t\t\tfill: theme.paletteText\n\t\t\t}\n\t\t);\n\t}\n\tfor (const node of graph.nodes) {\n\t\tconst c = geo.commits.get(node.id);\n\t\tif (!c) continue;\n\t\tmarks.push(...commitSymbol(c, theme));\n\t\tif (c.label !== undefined) marks.push(...commitLabel(c, c.label, geo.direction, theme));\n\t\tc.tags.forEach((t, i) => marks.push(...commitTag(c, t, i, geo.direction, theme)));\n\t}\n\treturn marks;\n};\n","// Follows Ursula's src/react/canvases/mindmap.tsx.\nimport { MINDMAP_METRICS, mindmapGeometry, type MindmapTopicGeometry } from '../../../diagram/mindmap/geometry.js';\nimport { sceneColor, type DiagramMark, type DiagramSceneTheme, type FamilyScene } from '../scene-kit.js';\n\nconst M = MINDMAP_METRICS;\n\ntype Box = { x: number; y: number; width: number; height: number };\n\nconst round = (v: number) => Math.round(v * 100) / 100;\n\n/** What a label line reads as (Ursula's `displayLabel`): no backticks, `<br>`s and `#…;` entities decoded. */\nfunction displayLabel(label: string): string {\n\tlet text = label;\n\tif (text.startsWith('`') && text.endsWith('`')) text = text.slice(1, -1);\n\treturn text\n\t\t.replace(/<br\\s*\\/?>/gi, '\\n')\n\t\t.replace(/#quot;/g, '\"')\n\t\t.replace(/#(\\d+);/g, (_, n) => String.fromCharCode(Number(n)));\n}\n\ninterface Arc {\n\trx: number;\n\try: number;\n\trotation: number;\n\tlarge: number;\n\tsweep: number;\n\tdx: number;\n\tdy: number;\n}\n\n/**\n * A closed outline of relative arcs from `(x, y)`, as path data plus its exact bounds\n * (each arc is sampled through its centre parameterisation, SVG spec F.6.5).\n */\nfunction arcOutline(x: number, y: number, arcs: Arc[]): { d: string; box: Box } {\n\tlet px = x;\n\tlet py = y;\n\tconst xs = [x];\n\tconst ys = [y];\n\tconst parts = [`M${round(x)},${round(y)}`];\n\tfor (const a of arcs) {\n\t\tparts.push(`a${round(a.rx)},${round(a.ry)} ${a.rotation} ${a.large},${a.sweep} ${round(a.dx)},${round(a.dy)}`);\n\t\tfor (const [sx, sy] of sampleArc(px, py, a)) {\n\t\t\txs.push(sx);\n\t\t\tys.push(sy);\n\t\t}\n\t\tpx += a.dx;\n\t\tpy += a.dy;\n\t}\n\t// Ursula closes with `H0 V0 Z` (back to the start).\n\tparts.push(`H${round(x)} V${round(y)} Z`);\n\tconst bx = Math.min(...xs);\n\tconst by = Math.min(...ys);\n\treturn { d: parts.join(' '), box: { x: bx, y: by, width: Math.max(...xs) - bx, height: Math.max(...ys) - by } };\n}\n\nfunction sampleArc(x1: number, y1: number, a: Arc): [number, number][] {\n\tconst x2 = x1 + a.dx;\n\tconst y2 = y1 + a.dy;\n\tlet rx = Math.abs(a.rx);\n\tlet ry = Math.abs(a.ry);\n\tif (!rx || !ry || (a.dx === 0 && a.dy === 0)) return [[x2, y2]];\n\tconst phi = (a.rotation * Math.PI) / 180;\n\tconst cos = Math.cos(phi);\n\tconst sin = Math.sin(phi);\n\tconst hx = (x1 - x2) / 2;\n\tconst hy = (y1 - y2) / 2;\n\tconst x1p = cos * hx + sin * hy;\n\tconst y1p = -sin * hx + cos * hy;\n\tconst lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);\n\tif (lambda > 1) {\n\t\trx *= Math.sqrt(lambda);\n\t\try *= Math.sqrt(lambda);\n\t}\n\tconst num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;\n\tconst den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;\n\tconst k = (a.large === a.sweep ? -1 : 1) * Math.sqrt(Math.max(0, num / den));\n\tconst cxp = (k * rx * y1p) / ry;\n\tconst cyp = (-k * ry * x1p) / rx;\n\tconst cx = cos * cxp - sin * cyp + (x1 + x2) / 2;\n\tconst cy = sin * cxp + cos * cyp + (y1 + y2) / 2;\n\tconst angle = (ux: number, uy: number, vx: number, vy: number) => Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);\n\tconst t1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);\n\tlet dt = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);\n\tif (!a.sweep && dt > 0) dt -= 2 * Math.PI;\n\tif (a.sweep && dt < 0) dt += 2 * Math.PI;\n\tconst out: [number, number][] = [];\n\tconst steps = 24;\n\tfor (let i = 1; i <= steps; i++) {\n\t\tconst t = t1 + (dt * i) / steps;\n\t\tout.push([cx + rx * Math.cos(t) * cos - ry * Math.sin(t) * sin, cy + rx * Math.cos(t) * sin + ry * Math.sin(t) * cos]);\n\t}\n\treturn out;\n}\n\nconst arc = (rx: number, ry: number, rotation: number, sweep: number, dx: number, dy: number): Arc => ({ rx, ry, rotation, large: 0, sweep, dx, dy });\n\n/** Mermaid's shape for a bracket form, in the topic's `w` × `h` box at (x0, y0). */\nfunction topicShape(t: MindmapTopicGeometry, x0: number, y0: number, fill: string, underline: string): DiagramMark[] {\n\tconst { width: w, height: h } = t;\n\tconst base = { target: t.id, role: 'topic' };\n\tconst key = `topic:${t.id}`;\n\tswitch (t.form) {\n\t\tcase 'circle':\n\t\t\treturn [{ kind: 'circle', key, ...base, cx: x0 + w / 2, cy: y0 + h / 2, r: w / 2, fill }];\n\t\tcase 'square':\n\t\t\treturn [{ kind: 'rect', key, ...base, x: x0, y: y0, width: w, height: h, fill }];\n\t\tcase 'rounded':\n\t\t\treturn [{ kind: 'rect', key, ...base, x: x0, y: y0, width: w, height: h, fill, radius: 5 }];\n\t\tcase 'hexagon': {\n\t\t\tconst m = h / 4;\n\t\t\tconst pts = [\n\t\t\t\t[m, h],\n\t\t\t\t[w - m, h],\n\t\t\t\t[w, h / 2],\n\t\t\t\t[w - m, 0],\n\t\t\t\t[m, 0],\n\t\t\t\t[0, h / 2]\n\t\t\t];\n\t\t\treturn [{ kind: 'path', key, ...base, d: `M${pts.map(([x, y]) => `${round(x0 + x!)},${round(y0 + y!)}`).join(' L')} Z`, fill }];\n\t\t}\n\t\tcase 'cloud': {\n\t\t\tconst r1 = 0.15 * w;\n\t\t\tconst r2 = 0.25 * w;\n\t\t\tconst r3 = 0.35 * w;\n\t\t\tconst r4 = 0.2 * w;\n\t\t\tconst { d, box } = arcOutline(x0, y0, [\n\t\t\t\tarc(r1, r1, 0, 1, w * 0.25, -w * 0.1),\n\t\t\t\tarc(r3, r3, 1, 1, w * 0.4, -w * 0.1),\n\t\t\t\tarc(r2, r2, 1, 1, w * 0.35, w * 0.2),\n\t\t\t\tarc(r1, r1, 1, 1, w * 0.15, h * 0.35),\n\t\t\t\tarc(r4, r4, 1, 1, -w * 0.15, h * 0.65),\n\t\t\t\tarc(r2, r1, 1, 1, -w * 0.25, w * 0.15),\n\t\t\t\tarc(r3, r3, 1, 1, -w * 0.5, 0),\n\t\t\t\tarc(r1, r1, 1, 1, -w * 0.25, -w * 0.15),\n\t\t\t\tarc(r1, r1, 1, 1, -w * 0.1, -h * 0.35),\n\t\t\t\tarc(r4, r4, 1, 1, w * 0.1, -h * 0.65)\n\t\t\t]);\n\t\t\treturn [{ kind: 'path', key, ...base, d, fill, box }];\n\t\t}\n\t\tcase 'bang': {\n\t\t\tconst r = 0.15 * w;\n\t\t\tconst { d, box } = arcOutline(x0, y0, [\n\t\t\t\tarc(r, r, 1, 0, w * 0.25, -h * 0.1),\n\t\t\t\tarc(r, r, 1, 0, w * 0.25, 0),\n\t\t\t\tarc(r, r, 1, 0, w * 0.25, 0),\n\t\t\t\tarc(r, r, 1, 0, w * 0.25, h * 0.1),\n\t\t\t\tarc(r, r, 1, 0, w * 0.15, h * 0.33),\n\t\t\t\tarc(r * 0.8, r * 0.8, 1, 0, 0, h * 0.34),\n\t\t\t\tarc(r, r, 1, 0, -w * 0.15, h * 0.33),\n\t\t\t\tarc(r, r, 1, 0, -w * 0.25, h * 0.15),\n\t\t\t\tarc(r, r, 1, 0, -w * 0.25, 0),\n\t\t\t\tarc(r, r, 1, 0, -w * 0.25, 0),\n\t\t\t\tarc(r, r, 1, 0, -w * 0.25, -h * 0.15),\n\t\t\t\tarc(r, r, 1, 0, -w * 0.1, -h * 0.33),\n\t\t\t\tarc(r * 0.8, r * 0.8, 1, 0, 0, -h * 0.34),\n\t\t\t\tarc(r, r, 1, 0, w * 0.1, -h * 0.33)\n\t\t\t]);\n\t\t\treturn [{ kind: 'path', key, ...base, d, fill, box }];\n\t\t}\n\t\tdefault:\n\t\t\t// `defaultMindmapNode`: a box with 5px corners and a line along the bottom.\n\t\t\treturn [\n\t\t\t\t{ kind: 'rect', key, ...base, x: x0, y: y0, width: w, height: h, fill, radius: 5 },\n\t\t\t\t{ kind: 'line', key: `underline:${t.id}`, role: 'topic-underline', target: t.id, x1: x0, y1: y0 + h, x2: x0 + w, y2: y0 + h, stroke: underline, strokeWidth: 3 }\n\t\t\t];\n\t}\n}\n\n/** Section colours: the root is drawn dark (Mermaid's `git0`), sections in palette order. */\nfunction topicColors(t: MindmapTopicGeometry, theme: DiagramSceneTheme): { fill: string; label: string; underline: string } {\n\t// `cScaleInv<n>` is the section hue's opposite: half way round the palette.\n\tconst underline = sceneColor(theme, t.section + Math.floor(theme.palette.length / 2));\n\tif (t.section < 0) return { fill: theme.nodeStroke, label: theme.background, underline };\n\treturn { fill: sceneColor(theme, t.section), label: theme.paletteText, underline };\n}\n\n/** A Font Awesome / Material icon class (`fa fa-book`) as one letter for the badge. */\nfunction iconGlyph(icon: string): string {\n\tconst name = icon.trim().split(/\\s+/).pop() ?? '';\n\tconst word = name.replace(/^(fa[a-z]?|mdi|bi|icon)-/, '');\n\treturn (word[0] ?? '?').toUpperCase();\n}\n\n/**\n * Mind maps as Mermaid draws them (Ursula's `canvases/mindmap.tsx`): straight branches\n * between topic centres in the section colour, thinner with depth, under the topics; the\n * root dark in the centre, every top-level branch a section in its palette colour, Mermaid's\n * node shapes per bracket form (default box with an underline, rounded, square, circle,\n * cloud, bang, hexagon), wrapped labels and an icon badge on the top-left corner. Mermaid\n * prints no title on mind maps. A topic's shape, label and badge select it.\n */\nexport const mindmapScene: FamilyScene = ({ graph, theme }) => {\n\tconst geo = mindmapGeometry(graph);\n\tconst marks: DiagramMark[] = [];\n\tfor (const e of geo.edges) {\n\t\tconst a = geo.topics.get(e.source);\n\t\tconst b = geo.topics.get(e.target);\n\t\tif (!a || !b) continue;\n\t\tmarks.push({ kind: 'line', key: `branch:${e.edgeId}`, role: 'branch', x1: a.x, y1: a.y, x2: b.x, y2: b.y, stroke: sceneColor(theme, e.section), strokeWidth: e.width });\n\t}\n\tfor (const node of graph.nodes) {\n\t\tconst t = geo.topics.get(node.id);\n\t\tif (!t) continue;\n\t\tconst x0 = t.x - t.width / 2;\n\t\tconst y0 = t.y - t.height / 2;\n\t\tconst colors = topicColors(t, theme);\n\t\tmarks.push(...topicShape(t, x0, y0, colors.fill, colors.underline));\n\t\tconst top = t.y - (t.lines.length * M.lineHeight) / 2;\n\t\tt.lines.forEach((line, i) => {\n\t\t\tconst text = displayLabel(line);\n\t\t\tif (!text) return;\n\t\t\tmarks.push({\n\t\t\t\tkind: 'text',\n\t\t\t\tkey: `label:${t.id}:${i}`,\n\t\t\t\trole: 'topic-label',\n\t\t\t\ttarget: t.id,\n\t\t\t\tx: t.x,\n\t\t\t\ty: top + (i + 0.5) * M.lineHeight,\n\t\t\t\ttext,\n\t\t\t\tanchor: 'middle',\n\t\t\t\tbaseline: 'middle',\n\t\t\t\tsize: M.fontSize,\n\t\t\t\tfill: colors.label\n\t\t\t});\n\t\t});\n\t\tconst icon = typeof node.data?.icon === 'string' ? node.data.icon : undefined;\n\t\tif (icon) {\n\t\t\tmarks.push(\n\t\t\t\t{ kind: 'circle', key: `icon:${t.id}`, role: 'icon', target: t.id, cx: x0, cy: y0, r: 11, fill: colors.fill, stroke: theme.background, strokeWidth: 2 },\n\t\t\t\t{ kind: 'text', key: `icon-glyph:${t.id}`, role: 'icon', target: t.id, x: x0, y: y0, text: iconGlyph(icon), anchor: 'middle', baseline: 'middle', size: 12, fill: colors.label }\n\t\t\t);\n\t\t}\n\t}\n\treturn marks;\n};\n","// Follows Ursula's src/react/canvases/c4.tsx (and the title node of src/react/canvas/Canvas.tsx).\nimport type { DiagramGraph } from '../../../diagram/types.js';\nimport { c4ArrowHead, c4BoundaryText, c4ElementGeometry, c4RelGeometry, type C4ElementType } from '../../../diagram/c4/geometry.js';\nimport type { DiagramLayout } from '../layout.js';\nimport { DIAGRAM_TITLE_TARGET, estimateTextWidth, type DiagramMark, type DiagramSceneTheme, type FamilyScene } from '../scene-kit.js';\n\n/** Mermaid's C4 element colours (`config.c4` `*_bg_color` / `*_border_color`), as Ursula hardcodes them. */\nconst C4_COLORS: Record<string, { bg: string; border: string }> = {\n\tperson: { bg: '#08427b', border: '#073b6f' },\n\tsystem: { bg: '#1168bd', border: '#3c7fc0' },\n\tcontainer: { bg: '#438dd5', border: '#3c7fc0' },\n\tcomponent: { bg: '#85bbf0', border: '#78a8d8' },\n\texternal_person: { bg: '#686868', border: '#8a8a8a' },\n\texternal_system: { bg: '#999999', border: '#8a8a8a' },\n\texternal_container: { bg: '#b3b3b3', border: '#a6a6a6' },\n\texternal_component: { bg: '#cccccc', border: '#bfbfbf' }\n};\nconst C4_ELEMENT_FG = '#ffffff';\n\nconst NAME_SIZE = 14;\nconst TYPE_SIZE = 10.5;\nconst DESCR_SIZE = 11.48;\nconst LINE_HEIGHT = 1.15;\nconst SECTION_GAP = 3;\nconst REL_FONT = 12;\nconst REL_LINE = 14;\nconst REL_GAP = 3;\n\nfunction colorsOf(type: C4ElementType): { bg: string; border: string } {\n\treturn C4_COLORS[`${type.external ? 'external_' : ''}${type.base}`] ?? C4_COLORS.container!;\n}\n\nconst styleOf = (data: Record<string, unknown> | undefined): Record<string, string> => (data?.c4Style ?? {}) as Record<string, string>;\n\n/** Ursula's title node: centred over the top-level items, 56px above the highest one. */\nfunction titleMark(graph: DiagramGraph, layout: DiagramLayout, theme: DiagramSceneTheme): DiagramMark | null {\n\tif (!graph.title) return null;\n\tconst groupIds = new Set(graph.groups.map((g) => g.id));\n\tconst roots = [...graph.nodes, ...graph.groups].filter((n) => !(n.parentId && groupIds.has(n.parentId)) && layout.positions[n.id] && layout.sizes[n.id]);\n\tconst minX = roots.length ? Math.min(...roots.map((n) => layout.positions[n.id]![0])) : 0;\n\tconst maxX = roots.length ? Math.max(...roots.map((n) => layout.positions[n.id]![0] + layout.sizes[n.id]!.width)) : 240;\n\tconst minY = roots.length ? Math.min(...roots.map((n) => layout.positions[n.id]![1])) : 0;\n\treturn { kind: 'text', key: 'title', role: 'title', target: DIAGRAM_TITLE_TARGET, x: (minX + maxX) / 2, y: minY - 56 + 18, text: graph.title, anchor: 'middle', baseline: 'middle', size: 18, weight: 600, fill: theme.text };\n}\n\n/**\n * C4 diagrams as Mermaid's C4 renderer draws them (Ursula's `canvases/c4.tsx`): boundaries as\n * dashed frames (deployment nodes solid) with the label, `[type]` and description centred at\n * the top; people as a head over a rounded body, systems, containers and components as rounded\n * rectangles, `*Db` as cylinders and `*Queue` as horizontal cylinders, in Mermaid's C4 colours\n * (`_Ext` grey, `UpdateElementStyle` overrides) with the bold name, the `[Type: technology]` line\n * and the description; relations as thin lines (the first straight, the rest Mermaid's curves)\n * with filled heads and the label and `[technology]` at the midpoint (numbered in `C4Dynamic`).\n * Every part of an element, boundary or relation selects it.\n */\nexport const c4Scene: FamilyScene = ({ graph, layout, theme }) => {\n\tconst marks: DiagramMark[] = [];\n\tconst box = (id: string) => {\n\t\tconst p = layout.positions[id];\n\t\tconst s = layout.sizes[id];\n\t\treturn p && s ? { x: p[0], y: p[1], width: s.width, height: s.height } : null;\n\t};\n\n\t// Boundaries, outermost first.\n\tconst groupIds = new Set(graph.groups.map((g) => g.id));\n\tconst depth = (id: string) => {\n\t\tlet d = 0;\n\t\tconst seen = new Set<string>();\n\t\tlet p = graph.groups.find((g) => g.id === id)?.parentId;\n\t\twhile (p && groupIds.has(p) && !seen.has(p)) {\n\t\t\tseen.add(p);\n\t\t\td++;\n\t\t\tp = graph.groups.find((g) => g.id === p)?.parentId;\n\t\t}\n\t\treturn d;\n\t};\n\tfor (const group of [...graph.groups].sort((a, b) => depth(a.id) - depth(b.id))) {\n\t\tconst b = box(group.id);\n\t\tif (!b) continue;\n\t\tconst text = c4BoundaryText(group);\n\t\tconst cx = b.x + b.width / 2;\n\t\tmarks.push({ kind: 'rect', key: `boundary:${group.id}`, role: 'boundary', target: group.id, ...b, radius: 2.5, fill: 'none', stroke: theme.groupStroke, strokeWidth: 1, dash: text.solid ? undefined : '7 7' });\n\t\tmarks.push({ kind: 'text', key: `boundary-label:${group.id}`, role: 'boundary-label', target: group.id, x: cx, y: b.y + text.labelY, text: text.label, anchor: 'middle', baseline: 'middle', size: 16, weight: 700, fill: theme.groupText });\n\t\tmarks.push({ kind: 'text', key: `boundary-type:${group.id}`, role: 'boundary-type', target: group.id, x: cx, y: b.y + text.typeY, text: text.type, anchor: 'middle', baseline: 'middle', size: 14, fill: theme.groupText });\n\t\tif (text.descr && text.descrY !== undefined)\n\t\t\tmarks.push({ kind: 'text', key: `boundary-descr:${group.id}`, role: 'boundary-descr', target: group.id, x: cx, y: b.y + text.descrY, text: text.descr, anchor: 'middle', baseline: 'middle', size: 12, fill: theme.groupText });\n\t}\n\n\t// Relation lines and heads (under the elements); their labels go on top, as in Mermaid.\n\tconst labels: DiagramMark[] = [];\n\tconst dynamic = graph.data?.c4Type === 'C4Dynamic';\n\tgraph.edges.forEach((edge, index) => {\n\t\tconst from = box(edge.source);\n\t\tconst to = box(edge.target);\n\t\tif (!from || !to) return;\n\t\tconst geo = c4RelGeometry(from, to, index);\n\t\tconst { start, end, control } = geo;\n\t\tconst style = styleOf(edge.data);\n\t\tconst line = style.lineColor || theme.edge;\n\t\tmarks.push({\n\t\t\tkind: 'path',\n\t\t\tkey: `relation:${edge.id}`,\n\t\t\trole: 'relation',\n\t\t\ttarget: edge.id,\n\t\t\td: control ? `M${start.x},${start.y} Q${control.x},${control.y} ${end.x},${end.y}` : `M${start.x},${start.y} L${end.x},${end.y}`,\n\t\t\tfill: 'none',\n\t\t\tstroke: line,\n\t\t\tstrokeWidth: 1\n\t\t});\n\t\tconst head = (key: string, points: string) =>\n\t\t\tmarks.push({ kind: 'path', key, role: 'arrow', target: edge.id, d: `M${points.split(' ').join(' L')} Z`, fill: line });\n\t\tif (edge.arrowEnd !== 'none') head(`arrow-end:${edge.id}`, c4ArrowHead(end, control ?? start));\n\t\tif (edge.arrowStart !== 'none') head(`arrow-start:${edge.id}`, c4ArrowHead(start, control ?? end));\n\n\t\tconst offsetX = parseInt(style.offsetX ?? '0', 10) || 0;\n\t\tconst offsetY = parseInt(style.offsetY ?? '0', 10) || 0;\n\t\tconst left = geo.label.x + offsetX;\n\t\tconst top = geo.label.y + offsetY - 7;\n\t\tconst textFill = style.textColor || theme.text;\n\t\tconst label = `${dynamic ? `${index + 1}: ` : ''}${edge.label ?? ''}`;\n\t\tconst tech = typeof edge.data?.tech === 'string' && edge.data.tech ? `[${edge.data.tech}]` : '';\n\t\tconst gridWidth = Math.max(label ? estimateTextWidth(label, REL_FONT) : 0, tech ? estimateTextWidth(tech, REL_FONT) : 0);\n\t\tif (label)\n\t\t\tlabels.push({ kind: 'text', key: `relation-label:${edge.id}`, role: 'relation-label', target: edge.id, x: left, y: top + REL_LINE / 2, text: label, anchor: 'start', baseline: 'middle', size: REL_FONT, fill: textFill });\n\t\tif (tech)\n\t\t\tlabels.push({\n\t\t\t\tkind: 'text',\n\t\t\t\tkey: `relation-tech:${edge.id}`,\n\t\t\t\trole: 'relation-tech',\n\t\t\t\ttarget: edge.id,\n\t\t\t\tx: left + gridWidth / 2,\n\t\t\t\ty: top + (label ? REL_LINE + REL_GAP : 0) + REL_LINE / 2,\n\t\t\t\ttext: tech,\n\t\t\t\tanchor: 'middle',\n\t\t\t\tbaseline: 'middle',\n\t\t\t\tsize: REL_FONT,\n\t\t\t\titalic: true,\n\t\t\t\tfill: style.textColor || theme.mutedText\n\t\t\t});\n\t});\n\n\t// Elements.\n\tfor (const node of graph.nodes) {\n\t\tconst b = box(node.id);\n\t\tif (!b) continue;\n\t\tconst geo = c4ElementGeometry(node);\n\t\tconst style = styleOf(node.data);\n\t\tconst colors = colorsOf(geo.type);\n\t\tconst fill = style.bgColor || colors.bg;\n\t\tconst stroke = style.borderColor || colors.border;\n\t\tconst fg = style.fontColor || C4_ELEMENT_FG;\n\t\tconst { x, y } = b;\n\t\tconst w = geo.width;\n\t\tconst h = geo.height;\n\t\tconst common = { target: node.id, fill, stroke, strokeWidth: 2 };\n\t\tif (geo.person) {\n\t\t\tconst { headRadius: r, bodyTop, bodyRadius } = geo.person;\n\t\t\tmarks.push(\n\t\t\t\t{ kind: 'rect', key: `element:${node.id}`, role: 'element', ...common, x, y: y + bodyTop, width: w, height: h - bodyTop, radius: bodyRadius },\n\t\t\t\t{ kind: 'circle', key: `element-head:${node.id}`, role: 'element', ...common, cx: x + w / 2, cy: y + r, r }\n\t\t\t);\n\t\t} else if (geo.shape === 'cylinder' && geo.cylinder) {\n\t\t\tconst { rx, ry } = geo.cylinder;\n\t\t\tconst body = h - 2 * ry;\n\t\t\tmarks.push({\n\t\t\t\tkind: 'path',\n\t\t\t\tkey: `element:${node.id}`,\n\t\t\t\trole: 'element',\n\t\t\t\t...common,\n\t\t\t\td: `M${x},${y + ry} a${rx},${ry} 0,0,0 ${w},0 a${rx},${ry} 0,0,0 ${-w},0 l0,${body} a${rx},${ry} 0,0,0 ${w},0 l0,${-body}`,\n\t\t\t\tbox: { x, y, width: w, height: h }\n\t\t\t});\n\t\t} else if (geo.shape === 'h-cyl' && geo.cylinder) {\n\t\t\tconst { rx, ry } = geo.cylinder;\n\t\t\tconst body = w - 2 * rx;\n\t\t\tmarks.push({\n\t\t\t\tkind: 'path',\n\t\t\t\tkey: `element:${node.id}`,\n\t\t\t\trole: 'element',\n\t\t\t\t...common,\n\t\t\t\td: `M${x + rx},${y + h} a${rx},${ry} 0,0,1 0,${-h} l${body},0 a${rx},${ry} 0,0,1 0,${h} M${x + rx + body},${y} a${rx},${ry} 0,0,0 0,${h} l${-body},0`,\n\t\t\t\tbox: { x, y, width: w, height: h }\n\t\t\t});\n\t\t} else marks.push({ kind: 'rect', key: `element:${node.id}`, role: 'element', ...common, x, y, width: w, height: h, radius: 12 });\n\n\t\t// Text: the sections stacked around the text centre, each line centred.\n\t\tlet top = y + geo.textCenter.y - geo.textHeight / 2;\n\t\tfor (const section of geo.sections) {\n\t\t\tconst size = section.part === 'label' ? NAME_SIZE : section.part === 'tech' ? TYPE_SIZE : DESCR_SIZE;\n\t\t\tconst lh = section.fontSize * LINE_HEIGHT;\n\t\t\tsection.lines.forEach((line, i) => {\n\t\t\t\tif (!line) return;\n\t\t\t\tmarks.push({\n\t\t\t\t\tkind: 'text',\n\t\t\t\t\tkey: `element-${section.part}:${node.id}:${i}`,\n\t\t\t\t\trole: section.part === 'label' ? 'element-name' : section.part === 'tech' ? 'element-type' : 'element-descr',\n\t\t\t\t\ttarget: node.id,\n\t\t\t\t\tx: x + geo.textCenter.x,\n\t\t\t\t\ty: top + (i + 0.5) * lh,\n\t\t\t\t\ttext: line,\n\t\t\t\t\tanchor: 'middle',\n\t\t\t\t\tbaseline: 'middle',\n\t\t\t\t\tsize,\n\t\t\t\t\tweight: section.bold ? 700 : undefined,\n\t\t\t\t\tfill: fg\n\t\t\t\t});\n\t\t\t});\n\t\t\ttop += section.height + SECTION_GAP;\n\t\t}\n\t}\n\n\tmarks.push(...labels);\n\tconst title = titleMark(graph, layout, theme);\n\tif (title) marks.push(title);\n\treturn marks;\n};\n","// Follows Ursula's src/react/canvases/architecture.tsx (and the title node of src/react/canvas/Canvas.tsx).\nimport type { DiagramGraph, GraphNode } from '../../../diagram/types.js';\nimport { ARCH_METRICS, architectureArrow, architectureEdgeRoute, isSide, serviceLabelLines, shiftEndpoint, type ArchSide } from '../../../diagram/architecture/geometry.js';\nimport type { DiagramLayout } from '../layout.js';\nimport { DIAGRAM_TITLE_TARGET, estimateTextWidth, type DiagramMark, type DiagramSceneTheme, type FamilyScene } from '../scene-kit.js';\n\nconst A = ARCH_METRICS;\n/** Mermaid's built-in icon square and glyph colours (Ursula's `--ursula-arch-icon-bg` / `-fg` defaults). */\nconst ICON_BG = '#087ebf';\nconst ICON_FG = '#ffffff';\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nconst r2 = (n: number) => Math.round(n * 100) / 100;\n\n/**\n * Rewrites an SVG path (M, L, H, V, C, S, Q, Z, absolute or relative; no arcs) as absolute\n * M / L / C / Q / Z commands, scaled by `s` and moved by (`ox`, `oy`), so Mermaid's 80×80 icon\n * glyphs can be drawn at any size in canvas coordinates.\n */\nfunction placePath(d: string, s: number, ox: number, oy: number): string {\n\tconst tokens = d.match(/[MmLlHhVvCcSsQqZz]|-?(?:\\d*\\.\\d+|\\d+\\.?)(?:e[-+]?\\d+)?/g) ?? [];\n\tconst out: string[] = [];\n\tconst P = (x: number, y: number) => `${r2(ox + x * s)},${r2(oy + y * s)}`;\n\tlet i = 0;\n\tlet cmd = '';\n\tlet x = 0, y = 0, sx = 0, sy = 0;\n\tlet lastC: [number, number] | null = null;\n\tconst num = () => Number(tokens[i++]);\n\twhile (i < tokens.length) {\n\t\tif (/[A-Za-z]/.test(tokens[i]!)) cmd = tokens[i++]!;\n\t\tconst rel = cmd === cmd.toLowerCase();\n\t\tconst bx = rel ? x : 0;\n\t\tconst by = rel ? y : 0;\n\t\tswitch (cmd.toUpperCase()) {\n\t\t\tcase 'M':\n\t\t\t\tx = bx + num();\n\t\t\t\ty = by + num();\n\t\t\t\tsx = x;\n\t\t\t\tsy = y;\n\t\t\t\tout.push(`M${P(x, y)}`);\n\t\t\t\tcmd = rel ? 'l' : 'L';\n\t\t\t\tlastC = null;\n\t\t\t\tbreak;\n\t\t\tcase 'L':\n\t\t\t\tx = bx + num();\n\t\t\t\ty = by + num();\n\t\t\t\tout.push(`L${P(x, y)}`);\n\t\t\t\tlastC = null;\n\t\t\t\tbreak;\n\t\t\tcase 'H':\n\t\t\t\tx = bx + num();\n\t\t\t\tout.push(`L${P(x, y)}`);\n\t\t\t\tlastC = null;\n\t\t\t\tbreak;\n\t\t\tcase 'V':\n\t\t\t\ty = (rel ? y : 0) + num();\n\t\t\t\tout.push(`L${P(x, y)}`);\n\t\t\t\tlastC = null;\n\t\t\t\tbreak;\n\t\t\tcase 'C': {\n\t\t\t\tconst c1x = bx + num(), c1y = by + num(), c2x = bx + num(), c2y = by + num();\n\t\t\t\tx = bx + num();\n\t\t\t\ty = by + num();\n\t\t\t\tout.push(`C${P(c1x, c1y)} ${P(c2x, c2y)} ${P(x, y)}`);\n\t\t\t\tlastC = [c2x, c2y];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'S': {\n\t\t\t\tconst c1x = lastC ? 2 * x - lastC[0] : x;\n\t\t\t\tconst c1y = lastC ? 2 * y - lastC[1] : y;\n\t\t\t\tconst c2x = bx + num(), c2y = by + num();\n\t\t\t\tx = bx + num();\n\t\t\t\ty = by + num();\n\t\t\t\tout.push(`C${P(c1x, c1y)} ${P(c2x, c2y)} ${P(x, y)}`);\n\t\t\t\tlastC = [c2x, c2y];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'Q': {\n\t\t\t\tconst cx = bx + num(), cy = by + num();\n\t\t\t\tx = bx + num();\n\t\t\t\ty = by + num();\n\t\t\t\tout.push(`Q${P(cx, cy)} ${P(x, y)}`);\n\t\t\t\tlastC = null;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'Z':\n\t\t\t\tx = sx;\n\t\t\t\ty = sy;\n\t\t\t\tout.push('Z');\n\t\t\t\tlastC = null;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\ti++;\n\t\t}\n\t}\n\treturn out.join(' ');\n}\n\n/** An ellipse as four cubic curves (glyph units). */\nfunction ellipse(cx: number, cy: number, rx: number, ry: number): string {\n\tconst k = 0.5523;\n\treturn `M${cx - rx},${cy} C${cx - rx},${cy - ry * k} ${cx - rx * k},${cy - ry} ${cx},${cy - ry} C${cx + rx * k},${cy - ry} ${cx + rx},${cy - ry * k} ${cx + rx},${cy} C${cx + rx},${cy + ry * k} ${cx + rx * k},${cy + ry} ${cx},${cy + ry} C${cx - rx * k},${cy + ry} ${cx - rx},${cy + ry * k} ${cx - rx},${cy} Z`;\n}\n\n/** A rounded rectangle (glyph units). */\nfunction roundRect(x: number, y: number, w: number, h: number, r: number): string {\n\treturn `M${x + r},${y} H${x + w - r} Q${x + w},${y} ${x + w},${y + r} V${y + h - r} Q${x + w},${y + h} ${x + w - r},${y + h} H${x + r} Q${x},${y + h} ${x},${y + h - r} V${y + r} Q${x},${y} ${x + r},${y} Z`;\n}\n\nconst line = (x1: number, y1: number, x2: number, y2: number) => `M${x1},${y1} L${x2},${y2}`;\n\n/** A glyph part: `stroke` parts are 2px outlines, `fill` parts solid. */\ntype GlyphPart = { d: string; paint: 'stroke' | 'fill' | 'both'; width?: number };\n\nconst BAR = (y: number): GlyphPart => ({ d: `m56.25,${y}c0,.27-.45.5-1,.5h-10.5c-.55,0-1-.23-1-.5s.45-.5,1-.5h10.5c.55,0,1,.23,1,.5Z`, paint: 'both', width: 1 });\nconst DOTS = (y: number): GlyphPart[] => [32.5, 27.5, 22.5].map((x) => ({ d: ellipse(x, y, 0.75, 0.75), paint: 'both', width: 1 }));\n\n/**\n * Mermaid's built-in architecture icons (`architectureIcons` in its renderer, 80×80), as paths:\n * the same outlines, with its `<ellipse>`, `<circle>`, `<rect>` and `<line>` elements written as\n * path data.\n */\nconst ARCH_GLYPHS: Record<string, GlyphPart[]> = {\n\tdatabase: [\n\t\t{ d: 'm20,57.86c0,3.94,8.95,7.14,20,7.14s20-3.2,20-7.14', paint: 'stroke' },\n\t\t{ d: 'm20,45.95c0,3.94,8.95,7.14,20,7.14s20-3.2,20-7.14', paint: 'stroke' },\n\t\t{ d: 'm20,34.05c0,3.94,8.95,7.14,20,7.14s20-3.2,20-7.14', paint: 'stroke' },\n\t\t{ d: ellipse(40, 22.14, 20, 7.14), paint: 'stroke' },\n\t\t{ d: line(20, 57.86, 20, 22.14), paint: 'stroke' },\n\t\t{ d: line(60, 57.86, 60, 22.14), paint: 'stroke' }\n\t],\n\tserver: [\n\t\t{ d: roundRect(17.5, 17.5, 45, 45, 2), paint: 'stroke' },\n\t\t{ d: line(17.5, 32.5, 62.5, 32.5), paint: 'stroke' },\n\t\t{ d: line(17.5, 47.5, 62.5, 47.5), paint: 'stroke' },\n\t\tBAR(25),\n\t\tBAR(40),\n\t\tBAR(55),\n\t\t...DOTS(25),\n\t\t...DOTS(40),\n\t\t...DOTS(55)\n\t],\n\tdisk: [\n\t\t{ d: roundRect(20, 15, 40, 50, 1), paint: 'stroke' },\n\t\t{ d: ellipse(24, 19.17, 0.8, 0.83), paint: 'stroke' },\n\t\t{ d: ellipse(56, 19.17, 0.8, 0.83), paint: 'stroke' },\n\t\t{ d: ellipse(24, 60.83, 0.8, 0.83), paint: 'stroke' },\n\t\t{ d: ellipse(56, 60.83, 0.8, 0.83), paint: 'stroke' },\n\t\t{ d: ellipse(40, 33.75, 14, 14.58), paint: 'stroke' },\n\t\t{ d: ellipse(40, 33.75, 4, 4.17), paint: 'both' },\n\t\t{ d: 'm37.51,42.52l-4.83,13.22c-.26.71-1.1,1.02-1.76.64l-4.18-2.42c-.66-.38-.81-1.26-.33-1.84l9.01-10.8c.88-1.05,2.56-.08,2.09,1.2Z', paint: 'fill' }\n\t],\n\tinternet: [\n\t\t{ d: ellipse(40, 40, 22.5, 22.5), paint: 'stroke' },\n\t\t{ d: line(40, 17.5, 40, 62.5), paint: 'stroke' },\n\t\t{ d: line(17.5, 40, 62.5, 40), paint: 'stroke' },\n\t\t{ d: 'm39.99,17.51c-15.28,11.1-15.28,33.88,0,44.98', paint: 'stroke' },\n\t\t{ d: 'm40.01,17.51c15.28,11.1,15.28,33.88,0,44.98', paint: 'stroke' },\n\t\t{ d: line(19.75, 30.1, 60.25, 30.1), paint: 'stroke' },\n\t\t{ d: line(19.75, 49.9, 60.25, 49.9), paint: 'stroke' }\n\t],\n\tcloud: [\n\t\t{\n\t\t\td: 'm65,47.5c0,2.76-2.24,5-5,5H20c-2.76,0-5-2.24-5-5,0-1.87,1.03-3.51,2.56-4.36-.04-.21-.06-.42-.06-.64,0-2.6,2.48-4.74,5.65-4.97,1.65-4.51,6.34-7.76,11.85-7.76.86,0,1.69.08,2.5.23,2.09-1.57,4.69-2.5,7.5-2.5,6.1,0,11.19,4.38,12.28,10.17,2.14.56,3.72,2.51,3.72,4.83,0,.03,0,.07-.01.1,2.29.46,4.01,2.48,4.01,4.9Z',\n\t\t\tpaint: 'stroke'\n\t\t}\n\t],\n\tblank: []\n};\n\n/**\n * A service or group icon at (`x`, `y`), `size` px: Mermaid's glyph on its blue square, the\n * icon name on the square for icons from an icon pack (`logos:aws-lambda`), or Mermaid's dashed\n * outline when there is no icon.\n */\nfunction iconMarks(key: string, target: string, icon: string | undefined, x: number, y: number, size: number, theme: DiagramSceneTheme): DiagramMark[] {\n\tconst s = size / 80;\n\tif (!icon)\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'path',\n\t\t\t\tkey: `icon:${key}`,\n\t\t\t\trole: 'icon',\n\t\t\t\ttarget,\n\t\t\t\td: placePath('M0,80 V5 Q0,0 5,0 H75 Q80,0 80,5 V80 Z', s, x, y),\n\t\t\t\tfill: 'none',\n\t\t\t\tstroke: theme.groupStroke,\n\t\t\t\tstrokeWidth: 2,\n\t\t\t\tdash: '8'\n\t\t\t}\n\t\t];\n\tconst marks: DiagramMark[] = [{ kind: 'rect', key: `icon:${key}`, role: 'icon', target, x, y, width: size, height: size, fill: ICON_BG }];\n\tconst glyph = ARCH_GLYPHS[icon];\n\tif (glyph) {\n\t\tglyph.forEach((part, i) =>\n\t\t\tmarks.push({\n\t\t\t\tkind: 'path',\n\t\t\t\tkey: `icon-glyph:${key}:${i}`,\n\t\t\t\trole: 'icon-glyph',\n\t\t\t\ttarget,\n\t\t\t\td: placePath(part.d, s, x, y),\n\t\t\t\tfill: part.paint === 'stroke' ? 'none' : ICON_FG,\n\t\t\t\tstroke: part.paint === 'fill' ? undefined : ICON_FG,\n\t\t\t\tstrokeWidth: (part.width ?? 2) * s\n\t\t\t})\n\t\t);\n\t\treturn marks;\n\t}\n\t// An icon Mermaid resolves from a registered icon pack: its name, broken to fit the square.\n\tconst font = 11 * s;\n\tconst perLine = Math.max(1, Math.floor((72 * s) / estimateTextWidth('m', font)));\n\tconst lines: string[] = [];\n\tfor (let i = 0; i < icon.length; i += perLine) lines.push(icon.slice(i, i + perLine));\n\tconst shown = lines.slice(0, Math.max(1, Math.floor((72 * s) / (font * 1.2))));\n\tconst lh = font * 1.2;\n\tshown.forEach((text, i) =>\n\t\tmarks.push({ kind: 'text', key: `icon-name:${key}:${i}`, role: 'icon-name', target, x: x + size / 2, y: y + size / 2 + (i - (shown.length - 1) / 2) * lh, text, anchor: 'middle', baseline: 'middle', size: font, fill: ICON_FG })\n\t);\n\treturn marks;\n}\n\n// ---------------------------------------------------------------------------\n// Scene\n// ---------------------------------------------------------------------------\n\nconst isJunction = (n: GraphNode) => n.shape === 'sm-circ' || !!n.data?.junction;\nconst iconOf = (data: Record<string, unknown> | undefined) => (typeof data?.icon === 'string' && data.icon ? data.icon : undefined);\n\n/** Ursula's title node: centred over the top-level items, 56px above the highest one. */\nfunction titleMark(graph: DiagramGraph, layout: DiagramLayout, theme: DiagramSceneTheme): DiagramMark | null {\n\tif (!graph.title) return null;\n\tconst groupIds = new Set(graph.groups.map((g) => g.id));\n\tconst roots = [...graph.nodes, ...graph.groups].filter((n) => !(n.parentId && groupIds.has(n.parentId)) && layout.positions[n.id] && layout.sizes[n.id]);\n\tconst minX = roots.length ? Math.min(...roots.map((n) => layout.positions[n.id]![0])) : 0;\n\tconst maxX = roots.length ? Math.max(...roots.map((n) => layout.positions[n.id]![0] + layout.sizes[n.id]!.width)) : 240;\n\tconst minY = roots.length ? Math.min(...roots.map((n) => layout.positions[n.id]![1])) : 0;\n\treturn { kind: 'text', key: 'title', role: 'title', target: DIAGRAM_TITLE_TARGET, x: (minX + maxX) / 2, y: minY - 56 + 18, text: graph.title, anchor: 'middle', baseline: 'middle', size: 18, weight: 600, fill: theme.text };\n}\n\n/**\n * `architecture-beta` diagrams as Mermaid draws them (Ursula's `canvases/architecture.tsx`):\n * groups as dashed frames with their icon and label at the top-left; services as Mermaid's 80px\n * icon squares (its `cloud`, `database`, `disk`, `internet` and `server` glyphs, the name on\n * the square for icon-pack icons, a dashed outline without an icon) with the label beneath;\n * junctions as points; edges leaving from their port sides as Mermaid's orthogonal lines with\n * triangular heads and the title along the line. Every part of a service, junction, group or\n * edge selects it.\n */\nexport const architectureScene: FamilyScene = ({ graph, layout, theme }) => {\n\tconst marks: DiagramMark[] = [];\n\tconst box = (id: string) => {\n\t\tconst p = layout.positions[id];\n\t\tconst s = layout.sizes[id];\n\t\treturn p && s ? { x: p[0], y: p[1], width: s.width, height: s.height } : null;\n\t};\n\n\t// Groups, outermost first.\n\tconst groupIds = new Set(graph.groups.map((g) => g.id));\n\tconst parentOf = new Map(graph.groups.map((g) => [g.id, g.parentId]));\n\tconst depth = (id: string) => {\n\t\tlet d = 0;\n\t\tconst seen = new Set<string>();\n\t\tlet p = parentOf.get(id);\n\t\twhile (p && groupIds.has(p) && !seen.has(p)) {\n\t\t\tseen.add(p);\n\t\t\td++;\n\t\t\tp = parentOf.get(p);\n\t\t}\n\t\treturn d;\n\t};\n\tfor (const group of [...graph.groups].sort((a, b) => depth(a.id) - depth(b.id))) {\n\t\tconst b = box(group.id);\n\t\tif (!b) continue;\n\t\tconst icon = iconOf(group.data);\n\t\tmarks.push({ kind: 'rect', key: `group:${group.id}`, role: 'group', target: group.id, ...b, fill: 'none', stroke: theme.groupStroke, strokeWidth: 2, dash: '8' });\n\t\tif (icon) marks.push(...iconMarks(`group:${group.id}`, group.id, icon, b.x + 1, b.y + 1, A.groupIconSize, theme));\n\t\tconst label = group.data?.untitled ? '' : group.label;\n\t\tif (label)\n\t\t\tmarks.push({ kind: 'text', key: `group-label:${group.id}`, role: 'group-label', target: group.id, x: b.x + (icon ? A.groupIconSize + 4 : 4), y: b.y + 7 + 19 / 2, text: label, anchor: 'start', baseline: 'middle', size: A.fontSize, fill: theme.groupText });\n\t}\n\n\t// Edges (under the services); titles go on top.\n\tconst labels: DiagramMark[] = [];\n\tconst junctions = new Set(graph.nodes.filter(isJunction).map((n) => n.id));\n\tconst port = (b: { x: number; y: number; width: number; height: number }, side: ArchSide) =>\n\t\tside === 'L' ? { x: b.x, y: b.y + b.height / 2 } : side === 'R' ? { x: b.x + b.width, y: b.y + b.height / 2 } : side === 'T' ? { x: b.x + b.width / 2, y: b.y } : { x: b.x + b.width / 2, y: b.y + b.height };\n\tfor (const edge of graph.edges) {\n\t\tconst from = box(edge.source);\n\t\tconst to = box(edge.target);\n\t\tif (!from || !to) continue;\n\t\tconst d = edge.data ?? {};\n\t\tconst sourceSide: ArchSide = isSide(d.sourceSide) ? d.sourceSide : 'R';\n\t\tconst targetSide: ArchSide = isSide(d.targetSide) ? d.targetSide : 'L';\n\t\tconst s = shiftEndpoint(port(from, sourceSide), sourceSide, { group: !!d.sourceGroup, junction: junctions.has(edge.source) });\n\t\tconst t = shiftEndpoint(port(to, targetSide), targetSide, { group: !!d.targetGroup, junction: junctions.has(edge.target) });\n\t\tconst route = architectureEdgeRoute(s, sourceSide, t, targetSide);\n\t\tmarks.push({ kind: 'path', key: `edge:${edge.id}`, role: 'edge', target: edge.id, d: route.points.map((p, i) => `${i ? 'L' : 'M'}${p.x},${p.y}`).join(' '), fill: 'none', stroke: theme.edge, strokeWidth: 3 });\n\t\tconst head = (key: string, points: string) => marks.push({ kind: 'path', key, role: 'arrow', target: edge.id, d: `M${points.split(' ').join(' L')} Z`, fill: theme.edge });\n\t\tif (edge.arrowStart !== 'none') head(`arrow-start:${edge.id}`, architectureArrow(s, sourceSide));\n\t\tif (edge.arrowEnd !== 'none') head(`arrow-end:${edge.id}`, architectureArrow(t, targetSide));\n\t\tif (edge.label)\n\t\t\tlabels.push({ kind: 'text', key: `edge-label:${edge.id}`, role: 'edge-label', target: edge.id, x: route.label.x, y: route.label.y, text: edge.label, anchor: 'middle', baseline: 'middle', size: A.fontSize, rotate: route.rotate || undefined, fill: theme.text });\n\t}\n\n\t// Services and junctions.\n\tfor (const node of graph.nodes) {\n\t\tconst b = box(node.id);\n\t\tif (!b) continue;\n\t\tif (isJunction(node)) {\n\t\t\tconst cx = b.x + b.width / 2;\n\t\t\tconst cy = b.y + b.height / 2;\n\t\t\t// The junction box is clickable; the dot is inset 3px as in Ursula.\n\t\t\tmarks.push(\n\t\t\t\t{ kind: 'circle', key: `junction:${node.id}`, role: 'junction', target: node.id, cx, cy, r: b.width / 2, fill: 'transparent' },\n\t\t\t\t{ kind: 'circle', key: `junction-dot:${node.id}`, role: 'junction', target: node.id, cx, cy, r: Math.max(1, b.width / 2 - 3), fill: theme.edge }\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\t\tmarks.push(...iconMarks(`service:${node.id}`, node.id, iconOf(node.data), b.x, b.y, b.width, theme));\n\t\tif (node.data?.untitled) continue;\n\t\tserviceLabelLines(node.label ?? '').forEach((text, i) =>\n\t\t\tmarks.push({ kind: 'text', key: `service-label:${node.id}:${i}`, role: 'service-label', target: node.id, x: b.x + b.width / 2, y: b.y + b.height + (i + 0.5) * A.lineHeight, text, anchor: 'middle', baseline: 'middle', size: A.fontSize, fill: theme.text })\n\t\t);\n\t}\n\n\tmarks.push(...labels);\n\tconst title = titleMark(graph, layout, theme);\n\tif (title) marks.push(title);\n\treturn marks;\n};\n","// Follows Ursula's src/react/canvases/gantt.tsx (and its .ursula-gantt__* styles).\nimport { GANTT_METRICS, ganttGeometry, type GanttRow } from '../../../diagram/gantt/geometry.js';\nimport type { GraphNode } from '../../../diagram/types.js';\nimport { DIAGRAM_TITLE_TARGET, sceneColor, type DiagramMark, type DiagramSceneTheme, type FamilyScene } from '../scene-kit.js';\n\nconst M = GANTT_METRICS;\n\n/** Mermaid's palette slot for critical tasks and the today line (red in both palettes). */\nconst CRIT = 3;\n\ntype Status = '' | 'done' | 'active' | 'crit' | 'done-crit' | 'active-crit';\n\nfunction statusOf(node: GraphNode | undefined): Status {\n\tconst tags = Array.isArray(node?.data?.tags) ? (node!.data!.tags as unknown[]).map(String) : [];\n\tconst done = tags.includes('done');\n\tconst active = tags.includes('active');\n\tconst crit = tags.includes('crit');\n\tif (crit && done) return 'done-crit';\n\tif (crit && active) return 'active-crit';\n\tif (crit) return 'crit';\n\tif (done) return 'done';\n\tif (active) return 'active';\n\treturn '';\n}\n\n/** Bar colours by status, as Mermaid's `taskBkgColor`, `activeTaskBkgColor`, `critBkgColor`… */\nfunction barColours(theme: DiagramSceneTheme, status: Status): { fill: string; stroke: string; text: string } {\n\tconst task = sceneColor(theme, 0);\n\tconst crit = sceneColor(theme, CRIT);\n\tswitch (status) {\n\t\tcase 'active':\n\t\t\treturn { fill: theme.background, stroke: task, text: theme.text };\n\t\tcase 'done':\n\t\t\treturn { fill: theme.grid, stroke: theme.mutedText, text: theme.text };\n\t\tcase 'crit':\n\t\t\treturn { fill: crit, stroke: crit, text: theme.paletteText };\n\t\tcase 'active-crit':\n\t\t\treturn { fill: theme.background, stroke: crit, text: theme.text };\n\t\tcase 'done-crit':\n\t\t\treturn { fill: theme.grid, stroke: crit, text: theme.text };\n\t\tdefault:\n\t\t\treturn { fill: task, stroke: task, text: theme.paletteText };\n\t}\n}\n\n/** Section band colours: Mermaid alternates `sectionBkgColor`, `altSectionBkgColor`, `sectionBkgColor2`. */\nfunction bandFill(theme: DiagramSceneTheme, index: number): string {\n\tconst slot = ((index % 4) + 4) % 4;\n\tif (slot === 1 || slot === 3) return theme.background;\n\treturn sceneColor(theme, slot === 0 ? 0 : 2);\n}\n\n/** Ursula's label-fit estimate: labels wider than the bar go to its right. */\nconst labelWidth = (text: string) => text.length * 6.8 + 16;\n\n/** A right-to-left step connector (React Flow's smooth step, without the rounded corners). */\nfunction stepPath(from: GanttRow, to: GanttRow): string {\n\tconst offset = 12;\n\tconst sx = from.x + from.width;\n\tconst sy = from.y + from.height / 2;\n\tconst tx = to.x;\n\tconst ty = to.y + to.height / 2;\n\tif (tx >= sx + 2 * offset) {\n\t\tconst mx = (sx + tx) / 2;\n\t\treturn `M ${sx} ${sy} L ${mx} ${sy} L ${mx} ${ty} L ${tx} ${ty}`;\n\t}\n\tconst my = (sy + ty) / 2;\n\treturn `M ${sx} ${sy} L ${sx + offset} ${sy} L ${sx + offset} ${my} L ${tx - offset} ${my} L ${tx - offset} ${ty} L ${tx} ${ty}`;\n}\n\n/**\n * Gantt charts as Mermaid draws them (Ursula's `canvases/gantt.tsx`): sections as full-width\n * bands at 20% opacity with their names on the left, grid lines with date ticks along the\n * bottom, the today line, one bar per task coloured by status (`done`, `active`, `crit` and\n * their combinations), milestones as diamonds, and the title centred above. A bar and its label\n * select the task; a band and its name select the section. `after` dependencies are not drawn by\n * Mermaid, so they appear only when they or one of their tasks is selected.\n */\nexport const ganttScene: FamilyScene = ({ graph, selection, theme }) => {\n\tconst geo = ganttGeometry(graph);\n\tconst nodes = new Map(graph.nodes.map((n) => [n.id, n]));\n\tconst marks: DiagramMark[] = [];\n\tconst left = geo.chartX - M.labelWidth;\n\tconst fullWidth = M.labelWidth + geo.chartWidth + M.rightPadding;\n\n\t// Bands (the loose band, for tasks before the first section, draws like section 0).\n\tfor (const band of geo.bands) {\n\t\tconst key = band.id ?? '::loose';\n\t\tmarks.push({\n\t\t\tkind: 'rect',\n\t\t\tkey: `band:${key}`,\n\t\t\trole: 'section',\n\t\t\ttarget: band.id,\n\t\t\tx: left,\n\t\t\ty: band.y,\n\t\t\twidth: fullWidth,\n\t\t\theight: band.height,\n\t\t\tfill: bandFill(theme, band.id ? band.index : 0),\n\t\t\topacity: 0.2\n\t\t});\n\t\tif (band.label !== undefined)\n\t\t\tmarks.push({ kind: 'text', key: `section:${key}`, role: 'section-label', target: band.id, x: left + 8, y: band.y + 4, text: band.label, anchor: 'start', baseline: 'top', size: 12, fill: theme.groupText });\n\t}\n\n\t// Grid lines, tick labels and the today line.\n\tfor (const tick of geo.ticks) {\n\t\tmarks.push(\n\t\t\t{ kind: 'line', key: `tick:${tick.time}`, role: 'grid', x1: tick.x, y1: geo.top, x2: tick.x, y2: geo.bottom, stroke: theme.grid, opacity: 0.8 },\n\t\t\t{ kind: 'text', key: `tick-label:${tick.time}`, role: 'axis', x: tick.x, y: geo.bottom + 6, text: tick.label, anchor: 'middle', baseline: 'top', size: 11, fill: theme.mutedText }\n\t\t);\n\t}\n\tif (geo.today !== undefined)\n\t\tmarks.push({ kind: 'line', key: 'today', role: 'today', x1: geo.today, y1: geo.top, x2: geo.today, y2: geo.bottom, stroke: sceneColor(theme, CRIT), strokeWidth: 2 });\n\n\t// Dependencies of the selection.\n\tfor (const edge of graph.edges) {\n\t\tif (!selection.has(edge.id) && !selection.has(edge.source) && !selection.has(edge.target)) continue;\n\t\tconst from = geo.rowById.get(edge.source);\n\t\tconst to = geo.rowById.get(edge.target);\n\t\tif (!from || !to) continue;\n\t\tmarks.push({ kind: 'path', key: `dependency:${edge.id}`, role: 'dependency', target: edge.id, d: stepPath(from, to), stroke: theme.selection, strokeWidth: 1.5, dash: '5 4', markerEnd: 'arrowclosed' });\n\t}\n\n\t// Tasks.\n\tfor (const row of geo.rows) {\n\t\tconst node = nodes.get(row.id);\n\t\tconst status = statusOf(node);\n\t\tconst colours = barColours(theme, status);\n\t\tconst label = (node?.label ?? row.id).replace(/<br\\s*\\/?>/gi, ' ');\n\t\tif (row.milestone) {\n\t\t\t// A square rotated 45° and scaled to 80%, as Ursula's diamond.\n\t\t\tconst cx = row.x + row.width / 2;\n\t\t\tconst cy = row.y + row.height / 2;\n\t\t\tconst h = (row.width * 0.8 * Math.SQRT2) / 2;\n\t\t\tconst crit = status.includes('crit');\n\t\t\tmarks.push({\n\t\t\t\tkind: 'path',\n\t\t\t\tkey: `milestone:${row.id}`,\n\t\t\t\trole: 'milestone',\n\t\t\t\ttarget: row.id,\n\t\t\t\td: `M ${cx} ${cy - h} L ${cx + h} ${cy} L ${cx} ${cy + h} L ${cx - h} ${cy} Z`,\n\t\t\t\tfill: status.startsWith('done') ? theme.grid : sceneColor(theme, 0),\n\t\t\t\tstroke: crit ? sceneColor(theme, CRIT) : status.startsWith('done') ? theme.mutedText : sceneColor(theme, 0),\n\t\t\t\tstrokeWidth: 2\n\t\t\t});\n\t\t} else {\n\t\t\tmarks.push({ kind: 'rect', key: `bar:${row.id}`, role: 'bar', target: row.id, x: row.x, y: row.y, width: row.width, height: row.height, fill: colours.fill, stroke: colours.stroke, strokeWidth: 2, radius: 3 });\n\t\t}\n\t\tconst outside = row.milestone || labelWidth(label) > row.width;\n\t\tmarks.push({\n\t\t\tkind: 'text',\n\t\t\tkey: `label:${row.id}`,\n\t\t\trole: 'task-label',\n\t\t\ttarget: row.id,\n\t\t\tx: outside ? row.x + row.width + 6 : row.x + row.width / 2,\n\t\t\ty: row.y + row.height / 2,\n\t\t\ttext: label,\n\t\t\tanchor: outside ? 'start' : 'middle',\n\t\t\tbaseline: 'middle',\n\t\t\tsize: 12,\n\t\t\tfill: outside ? theme.text : colours.text,\n\t\t\t...(row.milestone ? { italic: true } : {})\n\t\t});\n\t}\n\n\t// Title, centred above everything (Ursula's generic title node: 56px above the top).\n\tif (graph.title)\n\t\tmarks.push({ kind: 'text', key: 'title', role: 'title', target: DIAGRAM_TITLE_TARGET, x: left + fullWidth / 2, y: geo.top - 38, text: graph.title, anchor: 'middle', baseline: 'middle', size: 18, weight: 600, fill: theme.text });\n\treturn marks;\n};\n","// Follows Ursula's src/react/canvases/timeline.tsx (and its .ursula-timeline__* styles).\nimport { TIMELINE_METRICS, textWidth, timelineGeometry, type TimelineBox, type TimelineColour } from '../../../diagram/timeline/geometry.js';\nimport { DIAGRAM_TITLE_TARGET, sceneColor, type DiagramMark, type DiagramSceneTheme, type FamilyScene } from '../scene-kit.js';\n\nconst M = TIMELINE_METRICS;\nconst RADIUS = 5;\n/** Box text: 17px below the top, 16px with a 1.1 line height (Ursula's `.ursula-timeline__box`). */\nconst TEXT_TOP = 17;\nconst TEXT_LINE = M.fontSize * 1.1;\n\n/** Fill, text and bottom-line colours for a colour slot (Mermaid's `cScale`, `cScaleLabel`, `cScaleInv`). */\nfunction colours(theme: DiagramSceneTheme, colour: TimelineColour): { fill: string; text: string; line?: string } {\n\tif (colour === 'root') return { fill: theme.nodeStroke, text: theme.background };\n\treturn { fill: sceneColor(theme, colour), text: theme.paletteText, line: sceneColor(theme, colour + 6) };\n}\n\n/** A box with rounded top corners and a square bottom (Mermaid's `node-bkg` path). */\nfunction boxPath({ x, y, width: w, height: h }: TimelineBox): string {\n\tconst r = Math.min(RADIUS, w / 2, h / 2);\n\treturn `M ${x} ${y + r} Q ${x} ${y} ${x + r} ${y} L ${x + w - r} ${y} Q ${x + w} ${y} ${x + w} ${y + r} L ${x + w} ${y + h} L ${x} ${y + h} Z`;\n}\n\n/** Lines of `text` wrapped at `width` (word wrap, `<br>` breaks), like the geometry's `wrapLines`. */\nfunction wrap(text: string, width: number): string[] {\n\tconst out: string[] = [];\n\tfor (const part of String(text).split(/<br\\s*\\/?>|\\n/i)) {\n\t\tlet line = '';\n\t\tfor (const word of part.split(/\\s+/).filter(Boolean)) {\n\t\t\tconst next = line ? `${line} ${word}` : word;\n\t\t\tif (line && textWidth(next) > width) {\n\t\t\t\tout.push(line);\n\t\t\t\tline = word;\n\t\t\t} else line = next;\n\t\t}\n\t\tout.push(line);\n\t}\n\treturn out.length ? out : [''];\n}\n\n/** A line ending in Mermaid's timeline arrowhead (`M0,0 V4 L6,2 Z`, scaled by the stroke width). */\nfunction arrowLine(key: string, x1: number, y1: number, x2: number, y2: number, width: number, dashed: boolean, colour: string): DiagramMark[] {\n\tconst len = Math.hypot(x2 - x1, y2 - y1) || 1;\n\tconst ux = (x2 - x1) / len;\n\tconst uy = (y2 - y1) / len;\n\tconst bx = x2 - ux * 5 * width;\n\tconst by = y2 - uy * 5 * width;\n\tconst tx = x2 + ux * width;\n\tconst ty = y2 + uy * width;\n\treturn [\n\t\t{ kind: 'line', key, role: dashed ? 'event-line' : 'axis', x1, y1, x2, y2, stroke: colour, strokeWidth: width, ...(dashed ? { dash: '5,5' } : {}) },\n\t\t{\n\t\t\tkind: 'path',\n\t\t\tkey: `${key}:head`,\n\t\t\trole: dashed ? 'event-line' : 'axis',\n\t\t\td: `M ${bx - uy * 2 * width} ${by + ux * 2 * width} L ${tx} ${ty} L ${bx + uy * 2 * width} ${by - ux * 2 * width} Z`,\n\t\t\tfill: colour\n\t\t}\n\t];\n}\n\n/**\n * Timelines as Mermaid's left-to-right timeline draws them (Ursula's `canvases/timeline.tsx`):\n * section header bands on top, each period as a box in one row, a thick time axis with an\n * arrow under the periods, and each period's events stacked below it on a dashed line, with\n * the title left-aligned above. One colour per section, or one per period without sections.\n * A period and its events select the period; a section band selects the section.\n */\nexport const timelineScene: FamilyScene = ({ graph, theme }) => {\n\tconst geo = timelineGeometry(graph);\n\tconst marks: DiagramMark[] = [];\n\tconst nodes = new Map(graph.nodes.map((n) => [n.id, n]));\n\tconst groups = new Map(graph.groups.map((g) => [g.id, g]));\n\tconst line = theme.text;\n\n\tconst box = (key: string, role: string, target: string, b: TimelineBox, colour: TimelineColour, text: string, opacity?: number, brighten = false) => {\n\t\tconst c = colours(theme, colour);\n\t\tmarks.push({ kind: 'path', key, role, target, d: boxPath(b), fill: c.fill, ...(opacity !== undefined ? { opacity } : {}) });\n\t\t// Mermaid's `brightness(120%)` on events, as a translucent background-coloured wash.\n\t\tif (brighten) marks.push({ kind: 'path', key: `${key}:wash`, role, target, d: boxPath(b), fill: theme.background, opacity: 0.2 });\n\t\tif (c.line) marks.push({ kind: 'line', key: `${key}:line`, role, target, x1: b.x, y1: b.y + b.height, x2: b.x + b.width, y2: b.y + b.height, stroke: c.line, strokeWidth: 3 });\n\t\twrap(text, b.width - 2 * M.padding).forEach((t, i) =>\n\t\t\tmarks.push({ kind: 'text', key: `${key}:text:${i}`, role: `${role}-label`, target, x: b.x + b.width / 2, y: b.y + TEXT_TOP + TEXT_LINE * (i + 0.5), text: t, anchor: 'middle', baseline: 'middle', size: M.fontSize, fill: c.text })\n\t\t);\n\t};\n\n\t// Lines under everything else: the dashed event lines, then the axis.\n\tfor (const l of geo.eventLines) marks.push(...arrowLine(`event-line:${l.periodId}`, l.x, l.y1, l.x, l.y2, M.eventLineWidth, true, line));\n\tmarks.push(...arrowLine('axis', geo.axis.x1, geo.axis.y, geo.axis.x2, geo.axis.y, M.axisWidth, false, line));\n\n\t// Events (Mermaid draws them 20% brighter), then sections and periods.\n\tfor (const e of geo.events) box(`event:${e.periodId}:${e.index}`, 'event', e.periodId, e, e.colour, e.text, undefined, true);\n\tfor (const s of geo.sections.values()) box(`section:${s.id}`, 'section', s.id, s, s.colour, groups.get(s.id)?.label ?? s.id, s.empty ? 0.6 : undefined);\n\tfor (const p of geo.periods.values()) {\n\t\tconst node = nodes.get(p.id);\n\t\tbox(`period:${p.id}`, 'period', p.id, p, p.colour, node?.label ?? p.id);\n\t}\n\n\t// Mermaid: bold, 4ex, left-aligned at the title x, baseline 30px above the content.\n\tif (graph.title)\n\t\tmarks.push({ kind: 'text', key: 'title', role: 'title', target: DIAGRAM_TITLE_TARGET, x: geo.title.x, y: geo.title.baseline, text: graph.title, anchor: 'start', baseline: 'bottom', size: 32, weight: 700, fill: theme.text });\n\treturn marks;\n};\n","import type { DiagramFamily } from '../../../diagram/types.js';\nimport type { FamilyScene } from '../scene-kit.js';\nimport { pieScene } from './pie.js';\nimport { radarScene, xyChartScene } from './charts.js';\nimport { sequenceScene } from './sequence.js';\nimport { journeyScene } from './journey.js';\nimport { quadrantScene } from './quadrant.js';\nimport { gitGraphScene } from './gitgraph.js';\nimport { mindmapScene } from './mindmap.js';\nimport { c4Scene } from './c4.js';\nimport { architectureScene } from './architecture.js';\nimport { ganttScene } from './gantt.js';\nimport { timelineScene } from './timeline.js';\n\nexport { pieScene, radarScene, xyChartScene, sequenceScene, journeyScene, quadrantScene, gitGraphScene, mindmapScene, c4Scene, architectureScene, ganttScene, timelineScene };\nexport { radarChartSpec, xyChartSpec } from './charts.js';\n\n/** The drawings that ship with the package, by family. */\nexport const BUILTIN_FAMILY_SCENES: Partial<Record<DiagramFamily, FamilyScene>> = {\n\tpie: pieScene,\n\txychart: xyChartScene,\n\tradar: radarScene,\n\tsequence: sequenceScene,\n\tjourney: journeyScene,\n\tquadrant: quadrantScene,\n\tgitGraph: gitGraphScene,\n\tmindmap: mindmapScene,\n\tc4: c4Scene,\n\tarchitecture: architectureScene,\n\tgantt: ganttScene,\n\ttimeline: timelineScene\n};\n","// Family drawings follow Ursula's family canvases (src/react/canvases/*.tsx and\n// src/react/canvas/familyCanvas.ts), drawn as framework-free marks so React, Svelte and the\n// SVG export share one renderer per family.\nimport type { DiagramFamily, DiagramGraph, EditOp, MermaidDocument } from '../../diagram/types.js';\nimport { ensureDiagramLayout, type DiagramLayout, type DiagramLayoutOptions } from './layout.js';\nimport { BUILTIN_FAMILY_SCENES } from './families/index.js';\nimport { DIAGRAM_SCENE_LIGHT_THEME, DIAGRAM_TITLE_TARGET, estimateTextWidth, type DiagramMark, type DiagramSceneOptions, type DiagramScene, type DiagramSceneTheme, type DiagramTextMark, type FamilyScene } from './scene-kit.js';\n\nexport * from './scene-kit.js';\n\n// Seeded from a static map (not self-registering modules): the package is `sideEffects: false`.\nconst scenes = new Map<DiagramFamily, FamilyScene>(Object.entries(BUILTIN_FAMILY_SCENES) as [DiagramFamily, FamilyScene][]);\n\n/** Registers (or replaces) the drawing for a family. */\nexport function registerFamilyScene(family: DiagramFamily, scene: FamilyScene): void {\n\tscenes.set(family, scene);\n}\n\nexport function getFamilyScene(family: DiagramFamily): FamilyScene | undefined {\n\treturn scenes.get(family);\n}\n\n/** Whether a family is drawn as a scene (its own picture) instead of nodes and edges. */\nexport function hasFamilyScene(family: DiagramFamily): boolean {\n\treturn scenes.has(family);\n}\n\nfunction markBounds(mark: DiagramMark): [number, number, number, number] | null {\n\tswitch (mark.kind) {\n\t\tcase 'rect':\n\t\t\treturn [mark.x, mark.y, mark.x + mark.width, mark.y + mark.height];\n\t\tcase 'circle':\n\t\t\treturn [mark.cx - mark.r, mark.cy - mark.r, mark.cx + mark.r, mark.cy + mark.r];\n\t\tcase 'line':\n\t\t\treturn [Math.min(mark.x1, mark.x2), Math.min(mark.y1, mark.y2), Math.max(mark.x1, mark.x2), Math.max(mark.y1, mark.y2)];\n\t\tcase 'text': {\n\t\t\tconst w = estimateTextWidth(mark.text, mark.size);\n\t\t\tconst x0 = mark.anchor === 'start' ? mark.x : mark.anchor === 'end' ? mark.x - w : mark.x - w / 2;\n\t\t\tconst y0 = mark.baseline === 'top' ? mark.y : mark.baseline === 'bottom' ? mark.y - mark.size * 1.2 : mark.y - mark.size * 0.6;\n\t\t\tif (mark.rotate) {\n\t\t\t\tconst r = Math.max(w, mark.size * 1.2);\n\t\t\t\treturn [mark.x - r, mark.y - r, mark.x + r, mark.y + r];\n\t\t\t}\n\t\t\treturn [x0, y0, x0 + w, y0 + mark.size * 1.2];\n\t\t}\n\t\tcase 'path': {\n\t\t\tif (mark.box) return [mark.box.x, mark.box.y, mark.box.x + mark.box.width, mark.box.y + mark.box.height];\n\t\t\t// Absolute M / L / C / Q / Z paths list x, y pairs; arcs must give `box`.\n\t\t\tconst nums = mark.d.match(/-?\\d*\\.?\\d+(?:e-?\\d+)?/gi)?.map(Number) ?? [];\n\t\t\tif (nums.length < 2) return null;\n\t\t\tlet x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;\n\t\t\tfor (let i = 0; i + 1 < nums.length; i += 2) {\n\t\t\t\tx0 = Math.min(x0, nums[i]!);\n\t\t\t\ty0 = Math.min(y0, nums[i + 1]!);\n\t\t\t\tx1 = Math.max(x1, nums[i]!);\n\t\t\t\ty1 = Math.max(y1, nums[i + 1]!);\n\t\t\t}\n\t\t\treturn [x0, y0, x1, y1];\n\t\t}\n\t}\n}\n\n/** Bounds of a list of marks (all zero when empty). */\nexport function diagramMarksBounds(marks: readonly DiagramMark[]): { x: number; y: number; width: number; height: number } {\n\tlet x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;\n\tfor (const mark of marks) {\n\t\tconst b = markBounds(mark);\n\t\tif (!b) continue;\n\t\tx0 = Math.min(x0, b[0]);\n\t\ty0 = Math.min(y0, b[1]);\n\t\tx1 = Math.max(x1, b[2]);\n\t\ty1 = Math.max(y1, b[3]);\n\t}\n\tif (x0 === Infinity) return { x: 0, y: 0, width: 0, height: 0 };\n\treturn { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };\n}\n\nexport interface MermaidSceneOptions extends DiagramSceneOptions, DiagramLayoutOptions {\n\t/** A layout to draw instead of computing one. */\n\tlayout?: DiagramLayout;\n}\n\n/**\n * Draws a document as its family's own picture (pie wedges, Gantt bars on a date axis,\n * sequence lifelines, XY and radar charts…). Returns null for families drawn as plain nodes\n * and edges (flowchart, state, class, ER) and for text-only families.\n */\nexport function mermaidToScene(doc: MermaidDocument, options: MermaidSceneOptions = {}): DiagramScene | null {\n\tconst scene = scenes.get(doc.family);\n\tif (!scene || doc.tier !== 'editable') return null;\n\tconst layout = options.layout ?? ensureDiagramLayout(doc.graph, doc.meta, { family: doc.family, ...options });\n\treturn familySceneFromLayout(doc.family, doc.graph, layout, options);\n}\n\n/** {@link mermaidToScene} for a graph and layout already at hand (e.g. a tween frame). */\nexport function familySceneFromLayout(\n\tfamily: DiagramFamily,\n\tgraph: DiagramGraph,\n\tlayout: DiagramLayout,\n\toptions: DiagramSceneOptions = {}\n): DiagramScene | null {\n\tconst scene = scenes.get(family);\n\tif (!scene) return null;\n\tconst theme = { ...DIAGRAM_SCENE_LIGHT_THEME, ...options.theme };\n\tconst selection = new Set(options.selection ?? []);\n\tconst marks = scene({ graph, layout, theme, selection }).map((m) => (m.target && selection.has(m.target) ? { ...m, selected: true } : m));\n\treturn { family, ...diagramMarksBounds(marks), marks };\n}\n\nconst esc = (text: string) => text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\nconst num = (v: number) => String(Math.round(v * 100) / 100);\n\n/** SVG `dominant-baseline` for a text mark's baseline. */\nexport function diagramTextBaseline(baseline: DiagramTextMark['baseline']): 'hanging' | 'central' | 'text-after-edge' {\n\treturn baseline === 'top' ? 'hanging' : baseline === 'bottom' ? 'text-after-edge' : 'central';\n}\n\n/** Ids the SVG export uses for arrow markers (`<defs>` holds both). */\nexport const DIAGRAM_MARKER_IDS = { arrow: 'pd-arrow-open', arrowclosed: 'pd-arrow-closed' } as const;\n\n/** Serialises marks as SVG elements (no `<svg>` wrapper); markers refer to {@link DIAGRAM_MARKER_IDS}. */\nexport function diagramMarksToSvg(marks: readonly DiagramMark[], theme: Pick<DiagramSceneTheme, 'selection'> = DIAGRAM_SCENE_LIGHT_THEME): string {\n\tconst out: string[] = [];\n\tfor (const m of marks) {\n\t\tconst common = `${m.target ? ` data-target=\"${esc(m.target)}\"` : ''}${m.opacity !== undefined ? ` opacity=\"${num(m.opacity)}\"` : ''}`;\n\t\tconst sel = m.selected ? ` stroke=\"${esc(theme.selection)}\" stroke-width=\"2\"` : '';\n\t\tconst markers = (mk: { markerEnd?: string; markerStart?: string }) =>\n\t\t\t`${mk.markerEnd ? ` marker-end=\"url(#${DIAGRAM_MARKER_IDS[mk.markerEnd as 'arrow']})\"` : ''}${mk.markerStart ? ` marker-start=\"url(#${DIAGRAM_MARKER_IDS[mk.markerStart as 'arrow']})\"` : ''}`;\n\t\tswitch (m.kind) {\n\t\t\tcase 'rect':\n\t\t\t\tout.push(\n\t\t\t\t\t`<rect x=\"${num(m.x)}\" y=\"${num(m.y)}\" width=\"${num(Math.max(0, m.width))}\" height=\"${num(Math.max(0, m.height))}\"${m.radius ? ` rx=\"${num(m.radius)}\"` : ''} fill=\"${esc(m.fill)}\"${sel || (m.stroke ? ` stroke=\"${esc(m.stroke)}\" stroke-width=\"${num(m.strokeWidth ?? 1)}\"` : '')}${m.dash ? ` stroke-dasharray=\"${m.dash}\"` : ''}${common}/>`\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase 'circle':\n\t\t\t\tout.push(\n\t\t\t\t\t`<circle cx=\"${num(m.cx)}\" cy=\"${num(m.cy)}\" r=\"${num(m.r)}\" fill=\"${esc(m.fill)}\"${sel || (m.stroke ? ` stroke=\"${esc(m.stroke)}\" stroke-width=\"${num(m.strokeWidth ?? 1)}\"` : '')}${common}/>`\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase 'line':\n\t\t\t\tout.push(\n\t\t\t\t\t`<line x1=\"${num(m.x1)}\" y1=\"${num(m.y1)}\" x2=\"${num(m.x2)}\" y2=\"${num(m.y2)}\" stroke=\"${esc(m.selected ? theme.selection : m.stroke)}\" stroke-width=\"${num(m.strokeWidth ?? 1)}\"${m.dash ? ` stroke-dasharray=\"${m.dash}\"` : ''}${markers(m)}${common}/>`\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase 'path':\n\t\t\t\tout.push(\n\t\t\t\t\t`<path d=\"${m.d}\" fill=\"${esc(m.fill ?? 'none')}\"${sel || (m.stroke ? ` stroke=\"${esc(m.stroke)}\" stroke-width=\"${num(m.strokeWidth ?? 1)}\"` : '')}${m.dash ? ` stroke-dasharray=\"${m.dash}\"` : ''}${markers(m)}${common}/>`\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase 'text':\n\t\t\t\tout.push(\n\t\t\t\t\t`<text x=\"${num(m.x)}\" y=\"${num(m.y)}\" text-anchor=\"${m.anchor}\" dominant-baseline=\"${diagramTextBaseline(m.baseline)}\" font-size=\"${num(m.size)}\"${m.weight ? ` font-weight=\"${m.weight}\"` : ''}${m.italic ? ' font-style=\"italic\"' : ''} fill=\"${esc(m.fill)}\"${m.rotate ? ` transform=\"rotate(${num(m.rotate)} ${num(m.x)} ${num(m.y)})\"` : ''}${common}>${esc(m.text)}</text>`\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn out.join('');\n}\n\n/**\n * A scene theme that reads the page's `--background`, `--foreground`, `--muted-foreground`,\n * `--border` and `--primary` colour variables, with light fallbacks. The React and Svelte\n * scenes use it (they paint with `style`, where `var()` works); files and PNGs use\n * {@link DIAGRAM_SCENE_LIGHT_THEME} or the dark theme.\n */\nexport const CSS_VARIABLE_DIAGRAM_SCENE_THEME: DiagramSceneTheme = {\n\t...DIAGRAM_SCENE_LIGHT_THEME,\n\tbackground: 'var(--background, #ffffff)',\n\tnodeFill: 'var(--background, #ffffff)',\n\tnodeStroke: 'var(--foreground, #0f172a)',\n\ttext: 'var(--foreground, #0f172a)',\n\tedge: 'var(--muted-foreground, #64748b)',\n\tedgeLabelBackground: 'var(--background, #ffffff)',\n\tgroupFill: 'var(--muted, #f8fafc)',\n\tgroupStroke: 'var(--border, #cbd5e1)',\n\tgroupText: 'var(--muted-foreground, #475569)',\n\tgrid: 'var(--border, #cbd5e1)',\n\tmutedText: 'var(--muted-foreground, #64748b)',\n\tselection: 'var(--primary, #2563eb)',\n\tfontFamily: 'inherit'\n};\n\n/**\n * The edit that renames what a scene mark targets: the title (`setTitle`), a group\n * (`renameGroup`), an edge (`setEdgeLabel`) or a node (`renameNode`). Null for an unknown\n * target. An empty title removes it. Shared by the React and Svelte scenes' double-click.\n */\nexport function diagramRenameOp(doc: Pick<MermaidDocument, 'graph'>, target: string, label: string): EditOp | null {\n\tconst { graph } = doc;\n\tif (target === DIAGRAM_TITLE_TARGET) return { type: 'setTitle', title: label.trim() || undefined };\n\tif (graph.groups.some((g) => g.id === target)) return { type: 'renameGroup', id: target, label };\n\tif (graph.edges.some((e) => e.id === target)) return { type: 'setEdgeLabel', id: target, label };\n\tif (graph.nodes.some((n) => n.id === target)) return { type: 'renameNode', id: target, label };\n\treturn null;\n}\n\n/** The text a rename of `target` starts from (title, group, edge or node label). */\nexport function diagramTargetLabel(doc: Pick<MermaidDocument, 'graph'>, target: string): string | null {\n\tconst { graph } = doc;\n\tif (target === DIAGRAM_TITLE_TARGET) return graph.title ?? '';\n\treturn graph.groups.find((g) => g.id === target)?.label ?? graph.edges.find((e) => e.id === target)?.label ?? graph.nodes.find((n) => n.id === target)?.label ?? null;\n}\n","// Ported from Ursula's ShapeSvg (src/react/nodes/ShapeSvg.tsx), as path data so the React\n// and Svelte nodes draw the same geometry.\n\n/**\n * How a path is painted: `shape` with the node's fill and border, `line` with the border\n * only, `filled` solid in the border colour (state start / end dots, fork bars), `hidden`\n * not at all (text nodes keep a hit area).\n */\nexport type MermaidShapePaint = 'shape' | 'line' | 'filled' | 'hidden';\n\nexport interface MermaidShapePath {\n\td: string;\n\tpaint: MermaidShapePaint;\n}\n\ntype Pt = [number, number];\n\nconst n = (v: number) => Math.round(v * 100) / 100;\nconst poly = (points: Pt[]) => `M${points.map(([x, y]) => `${n(x)},${n(y)}`).join(' L')} Z`;\nconst rect = (x: number, y: number, w: number, h: number, r = 0) => {\n\tif (r <= 0) return poly([[x, y], [x + w, y], [x + w, y + h], [x, y + h]]);\n\tconst rr = Math.min(r, w / 2, h / 2);\n\treturn `M${n(x + rr)},${n(y)} H${n(x + w - rr)} A${n(rr)},${n(rr)} 0 0 1 ${n(x + w)},${n(y + rr)} V${n(y + h - rr)} A${n(rr)},${n(rr)} 0 0 1 ${n(x + w - rr)},${n(y + h)} H${n(x + rr)} A${n(rr)},${n(rr)} 0 0 1 ${n(x)},${n(y + h - rr)} V${n(y + rr)} A${n(rr)},${n(rr)} 0 0 1 ${n(x + rr)},${n(y)} Z`;\n};\nconst ellipse = (cx: number, cy: number, rx: number, ry: number) =>\n\t`M${n(cx - rx)},${n(cy)} A${n(rx)},${n(ry)} 0 1 0 ${n(cx + rx)},${n(cy)} A${n(rx)},${n(ry)} 0 1 0 ${n(cx - rx)},${n(cy)} Z`;\nconst line = (x1: number, y1: number, x2: number, y2: number) => `M${n(x1)},${n(y1)} L${n(x2)},${n(y2)}`;\n\n/**\n * Draws a Mermaid flowchart / state / class / ER shape (canonical names, see\n * `FLOWCHART_SHAPES` and `STATE_SHAPES`) as SVG paths that fill a `width × height`\n * box, inset by `inset` so strokes are not clipped. Unknown shapes draw a rectangle.\n */\nexport function mermaidShapePaths(shape: string, width: number, height: number, inset = 1): MermaidShapePath[] {\n\tconst s = inset;\n\tconst W = Math.max(0, width - 2 * s);\n\tconst H = Math.max(0, height - 2 * s);\n\tconst x0 = s;\n\tconst y0 = s;\n\tconst x1 = width - s;\n\tconst y1 = height - s;\n\tconst cx = width / 2;\n\tconst cy = height / 2;\n\tconst fill = (d: string): MermaidShapePath => ({ d, paint: 'shape' });\n\tconst stroke = (d: string): MermaidShapePath => ({ d, paint: 'line' });\n\tconst solid = (d: string): MermaidShapePath => ({ d, paint: 'filled' });\n\tswitch (shape) {\n\t\tcase 'rounded':\n\t\tcase 'event':\n\t\t\treturn [fill(rect(x0, y0, W, H, Math.min(10, H / 4)))];\n\t\tcase 'stadium':\n\t\t\treturn [fill(rect(x0, y0, W, H, H / 2))];\n\t\tcase 'subproc':\n\t\tcase 'subroutine': {\n\t\t\tconst i = Math.min(10, W / 8);\n\t\t\treturn [fill(rect(x0, y0, W, H)), stroke(line(x0 + i, y0, x0 + i, y1)), stroke(line(x1 - i, y0, x1 - i, y1))];\n\t\t}\n\t\tcase 'cyl':\n\t\tcase 'cylinder': {\n\t\t\tconst ry = Math.min(H / 6, 14);\n\t\t\treturn [\n\t\t\t\tfill(`M${n(x0)},${n(y0 + ry)} A${n(W / 2)},${n(ry)} 0 0 1 ${n(x1)},${n(y0 + ry)} L${n(x1)},${n(y1 - ry)} A${n(W / 2)},${n(ry)} 0 0 1 ${n(x0)},${n(y1 - ry)} Z`),\n\t\t\t\tstroke(`M${n(x0)},${n(y0 + ry)} A${n(W / 2)},${n(ry)} 0 0 0 ${n(x1)},${n(y0 + ry)}`)\n\t\t\t];\n\t\t}\n\t\tcase 'h-cyl':\n\t\tcase 'lin-cyl': {\n\t\t\tconst rx = Math.min(W / 6, 14);\n\t\t\tconst paths = [\n\t\t\t\tfill(`M${n(x0 + rx)},${n(y0)} L${n(x1 - rx)},${n(y0)} A${n(rx)},${n(H / 2)} 0 0 1 ${n(x1 - rx)},${n(y1)} L${n(x0 + rx)},${n(y1)} A${n(rx)},${n(H / 2)} 0 0 1 ${n(x0 + rx)},${n(y0)} Z`),\n\t\t\t\tstroke(ellipse(x1 - rx, cy, rx, H / 2))\n\t\t\t];\n\t\t\tif (shape === 'lin-cyl') paths.push(stroke(line(x0 + rx * 2, y0, x0 + rx * 2, y1)));\n\t\t\treturn paths;\n\t\t}\n\t\tcase 'circle':\n\t\tcase 'circ':\n\t\t\treturn [fill(ellipse(cx, cy, W / 2, H / 2))];\n\t\tcase 'dbl-circ':\n\t\t\treturn [fill(ellipse(cx, cy, W / 2, H / 2)), stroke(ellipse(cx, cy, Math.max(0, W / 2 - 5), Math.max(0, H / 2 - 5)))];\n\t\tcase 'sm-circ':\n\t\tcase 'f-circ':\n\t\tcase 'stateStart':\n\t\t\treturn [solid(ellipse(cx, cy, W / 2, H / 2))];\n\t\tcase 'fr-circ':\n\t\tcase 'stateEnd':\n\t\t\treturn [fill(ellipse(cx, cy, W / 2, H / 2)), solid(ellipse(cx, cy, Math.max(0, W / 2 - 5), Math.max(0, H / 2 - 5)))];\n\t\tcase 'cross-circ':\n\t\t\treturn [\n\t\t\t\tfill(ellipse(cx, cy, W / 2, H / 2)),\n\t\t\t\tstroke(line(cx - W / 2.83, cy - H / 2.83, cx + W / 2.83, cy + H / 2.83)),\n\t\t\t\tstroke(line(cx + W / 2.83, cy - H / 2.83, cx - W / 2.83, cy + H / 2.83))\n\t\t\t];\n\t\tcase 'odd':\n\t\tcase 'asymmetric': {\n\t\t\tconst d = Math.min(H / 2, 20);\n\t\t\treturn [fill(poly([[x0 + d, y0], [x1, y0], [x1, y1], [x0 + d, y1], [x0, cy]]))];\n\t\t}\n\t\tcase 'diam':\n\t\tcase 'diamond':\n\t\tcase 'choice':\n\t\t\treturn [fill(poly([[cx, y0], [x1, cy], [cx, y1], [x0, cy]]))];\n\t\tcase 'hex':\n\t\tcase 'hexagon': {\n\t\t\tconst d = Math.min(W / 5, H / 2);\n\t\t\treturn [fill(poly([[x0 + d, y0], [x1 - d, y0], [x1, cy], [x1 - d, y1], [x0 + d, y1], [x0, cy]]))];\n\t\t}\n\t\tcase 'lean-r': {\n\t\t\tconst d = Math.min(W / 5, H / 2);\n\t\t\treturn [fill(poly([[x0 + d, y0], [x1, y0], [x1 - d, y1], [x0, y1]]))];\n\t\t}\n\t\tcase 'lean-l': {\n\t\t\tconst d = Math.min(W / 5, H / 2);\n\t\t\treturn [fill(poly([[x0, y0], [x1 - d, y0], [x1, y1], [x0 + d, y1]]))];\n\t\t}\n\t\tcase 'trap-b': {\n\t\t\tconst d = Math.min(W / 5, H / 2);\n\t\t\treturn [fill(poly([[x0 + d, y0], [x1 - d, y0], [x1, y1], [x0, y1]]))];\n\t\t}\n\t\tcase 'trap-t': {\n\t\t\tconst d = Math.min(W / 5, H / 2);\n\t\t\treturn [fill(poly([[x0, y0], [x1, y0], [x1 - d, y1], [x0 + d, y1]]))];\n\t\t}\n\t\tcase 'text':\n\t\tcase 'anchor':\n\t\t\treturn [{ d: rect(x0, y0, W, H), paint: 'hidden' }];\n\t\tcase 'card':\n\t\tcase 'notch-rect': {\n\t\t\tconst d = Math.min(14, H / 3);\n\t\t\treturn [fill(poly([[x0 + d, y0], [x1, y0], [x1, y1], [x0, y1], [x0, y0 + d]]))];\n\t\t}\n\t\tcase 'lin-rect':\n\t\tcase 'shaded-process':\n\t\t\treturn [fill(rect(x0, y0, W, H)), stroke(line(x0 + 8, y0, x0 + 8, y1))];\n\t\tcase 'fork':\n\t\tcase 'join':\n\t\t\treturn [solid(rect(x0, y0, W, H, 2))];\n\t\tcase 'hourglass':\n\t\t\treturn [fill(poly([[x0, y0], [x1, y0], [x0, y1], [x1, y1]]))];\n\t\tcase 'bolt':\n\t\t\treturn [fill(poly([[cx + W * 0.1, y0], [x0 + W * 0.25, cy + 4], [cx, cy + 4], [cx - W * 0.1, y1], [x1 - W * 0.25, cy - 4], [cx, cy - 4]]))];\n\t\tcase 'doc':\n\t\tcase 'lin-doc':\n\t\tcase 'tag-doc': {\n\t\t\tconst wave = Math.min(12, H / 5);\n\t\t\tconst paths = [fill(`M${n(x0)},${n(y0)} L${n(x1)},${n(y0)} L${n(x1)},${n(y1 - wave)} Q${n(x1 - W / 4)},${n(y1 - wave * 2.2)} ${n(cx)},${n(y1 - wave)} T${n(x0)},${n(y1 - wave)} Z`)];\n\t\t\tif (shape === 'lin-doc') paths.push(stroke(line(x0 + 8, y0, x0 + 8, y1 - wave)));\n\t\t\tif (shape === 'tag-doc') paths.push(stroke(line(x1 - 12, y1 - wave * 1.6, x1, y1 - wave * 2.6)));\n\t\t\treturn paths;\n\t\t}\n\t\tcase 'docs': {\n\t\t\tconst wave = Math.min(10, H / 6);\n\t\t\tconst page = (dx: number, dy: number, inner: number) =>\n\t\t\t\tfill(`M${n(x0 + dx)},${n(y0 + dy)} L${n(x1 - inner)},${n(y0 + dy)} L${n(x1 - inner)},${n(y1 - wave - (12 - dy))} Q${n(x1 - W / 4 - inner)},${n(y1 - wave * 2.2 - (12 - dy))} ${n(cx + 6 - dy)},${n(y1 - wave - (12 - dy))} T${n(x0 + dx)},${n(y1 - wave - (12 - dy))} Z`);\n\t\t\treturn [page(12, 0, 0), page(6, 6, 6), page(0, 12, 12)];\n\t\t}\n\t\tcase 'delay':\n\t\t\treturn [fill(`M${n(x0)},${n(y0)} L${n(x1 - H / 2)},${n(y0)} A${n(H / 2)},${n(H / 2)} 0 0 1 ${n(x1 - H / 2)},${n(y1)} L${n(x0)},${n(y1)} Z`)];\n\t\tcase 'curv-trap': {\n\t\t\tconst d = Math.min(W / 6, H / 3);\n\t\t\treturn [fill(`M${n(x0 + d)},${n(y0)} L${n(x1 - d)},${n(y0)} Q${n(x1)},${n(y0)} ${n(x1)},${n(cy)} Q${n(x1)},${n(y1)} ${n(x1 - d)},${n(y1)} L${n(x0 + d)},${n(y1)} L${n(x0)},${n(cy)} Z`)];\n\t\t}\n\t\tcase 'div-rect': {\n\t\t\tconst d = Math.min(24, H / 3);\n\t\t\treturn [fill(rect(x0, y0, W, H)), stroke(line(x0, y0 + d, x1, y0 + d))];\n\t\t}\n\t\tcase 'tri':\n\t\t\treturn [fill(poly([[cx, y0], [x1, y1], [x0, y1]]))];\n\t\tcase 'flip-tri':\n\t\t\treturn [fill(poly([[x0, y0], [x1, y0], [cx, y1]]))];\n\t\tcase 'win-pane':\n\t\t\treturn [fill(rect(x0, y0, W, H)), stroke(line(x0 + 10, y0, x0 + 10, y1)), stroke(line(x0, y0 + 10, x1, y0 + 10))];\n\t\tcase 'flag': {\n\t\t\tconst wave = Math.min(10, H / 5);\n\t\t\treturn [fill(`M${n(x0)},${n(y0 + wave)} Q${n(x0 + W / 4)},${n(y0 - wave)} ${n(cx)},${n(y0 + wave)} T${n(x1)},${n(y0 + wave)} L${n(x1)},${n(y1 - wave)} Q${n(x1 - W / 4)},${n(y1 + wave)} ${n(cx)},${n(y1 - wave)} T${n(x0)},${n(y1 - wave)} Z`)];\n\t\t}\n\t\tcase 'st-rect':\n\t\t\treturn [fill(rect(x0 + 8, y0, W - 8, H - 8)), fill(rect(x0 + 4, y0 + 4, W - 8, H - 8)), fill(rect(x0, y0 + 8, W - 8, H - 8))];\n\t\tcase 'bow-rect': {\n\t\t\tconst d = Math.min(W / 8, 14);\n\t\t\treturn [fill(`M${n(x0)},${n(y0)} Q${n(x0 + d)},${n(cy)} ${n(x0)},${n(y1)} L${n(x1)},${n(y1)} Q${n(x1 - d)},${n(cy)} ${n(x1)},${n(y0)} Z`)];\n\t\t}\n\t\tcase 'tag-rect':\n\t\t\treturn [fill(rect(x0, y0, W, H)), stroke(line(x1 - 14, y1, x1, y1 - 14))];\n\t\tcase 'notch-pent': {\n\t\t\tconst d = Math.min(W / 6, H / 3);\n\t\t\treturn [fill(poly([[x0 + d, y0], [x1 - d, y0], [x1, y0 + d], [x1, y1], [x0, y1], [x0, y0 + d]]))];\n\t\t}\n\t\tcase 'sl-rect': {\n\t\t\tconst d = Math.min(H / 3, 20);\n\t\t\treturn [fill(poly([[x0, y0 + d], [x1, y0], [x1, y1], [x0, y1]]))];\n\t\t}\n\t\tcase 'brace':\n\t\tcase 'brace-l':\n\t\tcase 'brace-r':\n\t\tcase 'braces': {\n\t\t\tconst left = `M${n(x0 + 18)},${n(y0)} Q${n(x0 + 6)},${n(y0)} ${n(x0 + 6)},${n(cy - 6)} Q${n(x0 + 6)},${n(cy)} ${n(x0)},${n(cy)} Q${n(x0 + 6)},${n(cy)} ${n(x0 + 6)},${n(cy + 6)} Q${n(x0 + 6)},${n(y1)} ${n(x0 + 18)},${n(y1)}`;\n\t\t\tconst right = `M${n(x1 - 18)},${n(y0)} Q${n(x1 - 6)},${n(y0)} ${n(x1 - 6)},${n(cy - 6)} Q${n(x1 - 6)},${n(cy)} ${n(x1)},${n(cy)} Q${n(x1 - 6)},${n(cy)} ${n(x1 - 6)},${n(cy + 6)} Q${n(x1 - 6)},${n(y1)} ${n(x1 - 18)},${n(y1)}`;\n\t\t\tconst hit = { d: rect(x0, y0, W, H), paint: 'hidden' as const };\n\t\t\tif (shape === 'brace-r') return [hit, stroke(right)];\n\t\t\tif (shape === 'braces') return [hit, stroke(left), stroke(right)];\n\t\t\treturn [hit, stroke(left)];\n\t\t}\n\t\tcase 'comment':\n\t\t\treturn [{ d: rect(x0, y0, W, H), paint: 'hidden' }, stroke(`M${n(x0 + 14)},${n(y0)} Q${n(x0)},${n(y0)} ${n(x0)},${n(y0 + 14)} L${n(x0)},${n(y1 - 14)} Q${n(x0)},${n(y1)} ${n(x0 + 14)},${n(y1)}`)];\n\t\tcase 'icon':\n\t\tcase 'image':\n\t\t\treturn [fill(rect(x0, y0, W, H, 6))];\n\t\tcase 'note':\n\t\t\treturn [fill(poly([[x0, y0], [x1 - 12, y0], [x1, y0 + 12], [x1, y1], [x0, y1]]))];\n\t\tdefault:\n\t\t\treturn [fill(rect(x0, y0, W, H))];\n\t}\n}\n\n/**\n * The arrow marker to draw for a Mermaid arrow head on a canvas edge: `arrowclosed` for\n * the filled heads (`-->`, `<|--`, ER cardinalities fall back to it), `arrow` for open\n * ones, or `null` for none. Canvas libraries map these to their own marker types.\n */\nexport function mermaidEdgeMarker(head: string): 'arrow' | 'arrowclosed' | null {\n\tif (head === 'none') return null;\n\tif (head === 'arrow_open' || head === 'dependency') return 'arrow';\n\treturn 'arrowclosed';\n}\n\n\n/**\n * The CSS custom properties and colours a Mermaid node paints with, from its resolved\n * `style` / `classDef` declarations. Unstyled nodes use the host's `--card`, `--foreground`\n * and `--primary` colours (with fallbacks), so they follow the app theme.\n */\nexport function mermaidNodePaint(styles: Record<string, string> = {}, selected = false): Record<string, string> {\n\tconst paint: Record<string, string> = {\n\t\t'--pixerate-diagram-node-bg': styles.fill ?? 'var(--card, var(--background, #ffffff))',\n\t\t'--pixerate-diagram-node-border': selected ? 'var(--primary, #2563eb)' : (styles.stroke ?? 'var(--foreground, #0f172a)'),\n\t\tcolor: styles.color ?? 'var(--foreground, #0f172a)',\n\t\t'stroke-width': styles['stroke-width'] ?? (selected ? '2px' : '1.25px')\n\t};\n\tif (styles['stroke-dasharray']) paint['stroke-dasharray'] = styles['stroke-dasharray'];\n\treturn paint;\n}\n\n/** How each {@link MermaidShapePaint} fills and strokes, as CSS declarations. */\nexport const MERMAID_SHAPE_PAINT: Record<MermaidShapePaint, { fill: string; stroke: string }> = {\n\tshape: { fill: 'var(--pixerate-diagram-node-bg)', stroke: 'var(--pixerate-diagram-node-border)' },\n\tline: { fill: 'none', stroke: 'var(--pixerate-diagram-node-border)' },\n\tfilled: { fill: 'var(--pixerate-diagram-node-border)', stroke: 'var(--pixerate-diagram-node-border)' },\n\thidden: { fill: 'transparent', stroke: 'none' }\n};\n\n/** A Mermaid label as plain lines: `<br>` splits lines, other tags and markdown-string quotes are dropped. */\nexport function mermaidLabelLines(label: string): string[] {\n\treturn String(label)\n\t\t.replace(/^\"`?|`?\"$/g, '')\n\t\t.split(/<br\\s*\\/?>|\\n/i)\n\t\t.map((line) => line.replace(/<[^>]+>/g, ''));\n}\n\n/** Class members / ER attributes of a node, one display line each. */\nexport function mermaidNodeMembers(data: Record<string, unknown> | undefined): string[] {\n\tconst list = [...((data?.members as Array<{ raw?: string }>) ?? []), ...((data?.attributes as Array<{ raw?: string }>) ?? [])];\n\treturn list.map((m) => m.raw ?? '');\n}\n","// Thumbnails and PNG export follow Ursula's MermaidThumbnail and src/react/export.ts\n// (`downloadCanvasPng`, a longest-side bound on snapshots), drawn from the layout as standalone\n// SVG instead of capturing the DOM, so they work without the canvas mounted.\nimport type { DiagramGraph, MermaidDocument } from '../../diagram/types.js';\nimport { ensureDiagramLayout, type DiagramLayout, type DiagramLayoutOptions } from './layout.js';\nimport { mermaidEdgeMarker, mermaidLabelLines, mermaidNodeMembers, mermaidShapePaths } from './shapes.js';\nimport { mermaidEdgeDash, resolveMermaidStyles } from './toFlow.js';\nimport { DIAGRAM_SVG_LIGHT_THEME } from './theme.js';\nimport { DIAGRAM_SCENE_LIGHT_THEME, diagramMarksToSvg, familySceneFromLayout, type DiagramSceneTheme } from './scene.js';\n\nexport { DIAGRAM_SVG_DARK_THEME, DIAGRAM_SVG_LIGHT_THEME, type DiagramSvgTheme } from './theme.js';\n\nexport interface DiagramSvgOptions extends DiagramLayoutOptions {\n\t/** A layout to draw instead of computing one (e.g. the canvas' current layout). */\n\tlayout?: DiagramLayout;\n\t/** Colours; the scene colours (`palette`, `grid`…) are used by families drawn as scenes. */\n\ttheme?: Partial<DiagramSceneTheme>;\n\t/** Room around the diagram in px (default 16). */\n\tpadding?: number;\n\t/**\n\t * Fit inside this box (a thumbnail): the SVG's `width` / `height` shrink to fit, keeping the\n\t * aspect ratio; the drawing is not enlarged. Default: natural size.\n\t */\n\tmaxWidth?: number;\n\tmaxHeight?: number;\n\t/** Accessible name (`<title>`); default the diagram's title or family. */\n\ttitle?: string;\n\t/** Leave out the background rectangle (transparent). */\n\ttransparent?: boolean;\n}\n\nexport interface DiagramSvg {\n\tsvg: string;\n\t/** The SVG's `width` / `height` attributes (after fitting). */\n\twidth: number;\n\theight: number;\n\t/** Natural size of the drawing (before fitting), in px. */\n\tnaturalWidth: number;\n\tnaturalHeight: number;\n}\n\nconst esc = (text: string) => text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\nconst n = (v: number) => String(Math.round(v * 100) / 100);\n\ninterface Box {\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n}\n\n/** Where the segment from the box's centre towards `to` leaves the box. */\nfunction exitPoint(box: Box, to: { x: number; y: number }): { x: number; y: number } {\n\tconst cx = box.x + box.width / 2;\n\tconst cy = box.y + box.height / 2;\n\tconst dx = to.x - cx;\n\tconst dy = to.y - cy;\n\tif (dx === 0 && dy === 0) return { x: cx, y: cy };\n\tconst sx = dx === 0 ? Infinity : box.width / 2 / Math.abs(dx);\n\tconst sy = dy === 0 ? Infinity : box.height / 2 / Math.abs(dy);\n\tconst t = Math.min(sx, sy);\n\treturn { x: cx + dx * t, y: cy + dy * t };\n}\n\n/**\n * Draws a Mermaid diagram as a standalone SVG. Flowchart, state, class and ER diagrams: groups,\n * nodes in their Mermaid shapes with `style` / `classDef` colours, labels, class members and\n * ER attributes, and edges with labels, line patterns and arrow heads. Families with their own\n * picture (pie, Gantt, sequence, XY, radar…) are drawn from their scene (`mermaidToScene`).\n * Uses stored `%% @pixerate` positions (auto-layout for the rest), so it matches the canvas.\n * Pure: no DOM.\n */\nexport function mermaidToSvg(doc: MermaidDocument, options: DiagramSvgOptions = {}): DiagramSvg {\n\tconst theme = { ...DIAGRAM_SVG_LIGHT_THEME, ...options.theme };\n\tconst pad = options.padding ?? 16;\n\tconst graph: DiagramGraph = doc.graph;\n\tconst layout = options.layout ?? ensureDiagramLayout(graph, doc.meta, { family: doc.family, ...options });\n\tconst boxOf = (id: string): Box | null => {\n\t\tconst p = layout.positions[id];\n\t\tif (!p) return null;\n\t\tconst s = layout.sizes[id] ?? { width: 160, height: 56 };\n\t\treturn { x: p[0], y: p[1], width: s.width, height: s.height };\n\t};\n\tconst scene = doc.tier === 'editable' ? familySceneFromLayout(doc.family, graph, layout, { theme: { ...DIAGRAM_SCENE_LIGHT_THEME, ...theme } }) : null;\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tif (scene && scene.marks.length) {\n\t\tminX = scene.x;\n\t\tminY = scene.y;\n\t\tmaxX = scene.x + scene.width;\n\t\tmaxY = scene.y + scene.height;\n\t} else for (const id of Object.keys(layout.positions)) {\n\t\tconst b = boxOf(id)!;\n\t\tminX = Math.min(minX, b.x);\n\t\tminY = Math.min(minY, b.y);\n\t\tmaxX = Math.max(maxX, b.x + b.width);\n\t\tmaxY = Math.max(maxY, b.y + b.height);\n\t}\n\tif (minX === Infinity) minX = minY = maxX = maxY = 0;\n\tconst naturalWidth = Math.max(1, Math.ceil(maxX - minX + pad * 2));\n\tconst naturalHeight = Math.max(1, Math.ceil(maxY - minY + pad * 2));\n\tconst fit = Math.min(1, (options.maxWidth ?? Infinity) / naturalWidth, (options.maxHeight ?? Infinity) / naturalHeight);\n\tconst width = Math.max(1, Math.round(naturalWidth * fit));\n\tconst height = Math.max(1, Math.round(naturalHeight * fit));\n\tconst ox = pad - minX;\n\tconst oy = pad - minY;\n\tconst fs = theme.fontSize;\n\tconst title = options.title ?? graph.title ?? `${doc.family} diagram`;\n\tconst out: string[] = [];\n\tout.push(\n\t\t`<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${naturalWidth} ${naturalHeight}\" role=\"img\" aria-label=\"${esc(title)}\" font-family=\"${esc(theme.fontFamily)}\" font-size=\"${fs}\">`,\n\t\t`<title>${esc(title)}</title>`,\n\t\t'<defs>',\n\t\t`<marker id=\"pd-arrow-closed\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"8\" markerHeight=\"8\" orient=\"auto-start-reverse\"><path d=\"M0,0 L10,5 L0,10 Z\" fill=\"${esc(theme.edge)}\"/></marker>`,\n\t\t`<marker id=\"pd-arrow-open\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"8\" markerHeight=\"8\" orient=\"auto-start-reverse\"><path d=\"M0,0 L10,5 L0,10\" fill=\"none\" stroke=\"${esc(theme.edge)}\" stroke-width=\"1.5\"/></marker>`,\n\t\t'</defs>'\n\t);\n\tif (!options.transparent) out.push(`<rect width=\"${naturalWidth}\" height=\"${naturalHeight}\" fill=\"${esc(theme.background)}\"/>`);\n\tout.push(`<g transform=\"translate(${n(ox)} ${n(oy)})\">`);\n\tif (scene) {\n\t\tout.push(diagramMarksToSvg(scene.marks, { ...DIAGRAM_SCENE_LIGHT_THEME, ...theme }), '</g></svg>');\n\t\treturn { svg: out.join(''), width, height, naturalWidth, naturalHeight };\n\t}\n\n\t// Groups, outermost first.\n\tconst depth = (id: string) => {\n\t\tlet d = 0;\n\t\tlet cur = graph.groups.find((g) => g.id === id)?.parentId;\n\t\twhile (cur && d < 50) {\n\t\t\td++;\n\t\t\tcur = graph.groups.find((g) => g.id === cur)?.parentId;\n\t\t}\n\t\treturn d;\n\t};\n\tfor (const g of [...graph.groups].sort((a, b) => depth(a.id) - depth(b.id))) {\n\t\tconst b = boxOf(g.id);\n\t\tif (!b) continue;\n\t\tconst st = resolveMermaidStyles(g.classes, g.styles, graph.classDefs);\n\t\tout.push(\n\t\t\t`<g data-group=\"${esc(g.id)}\"><rect x=\"${n(b.x)}\" y=\"${n(b.y)}\" width=\"${n(b.width)}\" height=\"${n(b.height)}\" rx=\"8\" fill=\"${esc(st.fill ?? theme.groupFill)}\" stroke=\"${esc(st.stroke ?? theme.groupStroke)}\" stroke-dasharray=\"4 3\"/>`,\n\t\t\t`<text x=\"${n(b.x + 10)}\" y=\"${n(b.y + 18)}\" fill=\"${esc(st.color ?? theme.groupText)}\" font-size=\"${fs - 1}\" font-weight=\"600\">${esc(mermaidLabelLines(g.label).join(' '))}</text></g>`\n\t\t);\n\t}\n\n\t// Edges under nodes.\n\tfor (const e of graph.edges) {\n\t\tconst a = boxOf(e.source);\n\t\tconst b = boxOf(e.target);\n\t\tif (!a || !b || e.pattern === 'invisible') continue;\n\t\tconst ca = { x: a.x + a.width / 2, y: a.y + a.height / 2 };\n\t\tconst cb = { x: b.x + b.width / 2, y: b.y + b.height / 2 };\n\t\tconst p = exitPoint(a, cb);\n\t\tconst q = exitPoint(b, ca);\n\t\tconst st = resolveMermaidStyles(e.classes, e.styles, graph.classDefs);\n\t\tconst end = mermaidEdgeMarker(e.arrowEnd);\n\t\tconst start = mermaidEdgeMarker(e.arrowStart);\n\t\tconst marker = (m: string | null) => (m === 'arrow' ? 'url(#pd-arrow-open)' : m ? 'url(#pd-arrow-closed)' : '');\n\t\tconst dash = mermaidEdgeDash(e.pattern);\n\t\tout.push(\n\t\t\t`<line data-edge=\"${esc(e.id)}\" x1=\"${n(p.x)}\" y1=\"${n(p.y)}\" x2=\"${n(q.x)}\" y2=\"${n(q.y)}\" stroke=\"${esc(st.stroke ?? theme.edge)}\" stroke-width=\"${e.pattern === 'thick' ? 3 : 1.5}\"${dash ? ` stroke-dasharray=\"${dash}\"` : ''}${end ? ` marker-end=\"${marker(end)}\"` : ''}${start ? ` marker-start=\"${marker(start)}\"` : ''}/>`\n\t\t);\n\t\tif (e.label) {\n\t\t\tconst text = mermaidLabelLines(e.label).join(' ');\n\t\t\tconst w = text.length * fs * 0.55 + 10;\n\t\t\tconst mx = (p.x + q.x) / 2;\n\t\t\tconst my = (p.y + q.y) / 2;\n\t\t\tout.push(\n\t\t\t\t`<rect x=\"${n(mx - w / 2)}\" y=\"${n(my - fs * 0.75)}\" width=\"${n(w)}\" height=\"${n(fs * 1.5)}\" rx=\"3\" fill=\"${esc(theme.edgeLabelBackground)}\"/>`,\n\t\t\t\t`<text x=\"${n(mx)}\" y=\"${n(my)}\" text-anchor=\"middle\" dominant-baseline=\"central\" fill=\"${esc(st.color ?? theme.text)}\" font-size=\"${fs - 1}\">${esc(text)}</text>`\n\t\t\t);\n\t\t}\n\t}\n\n\t// Nodes.\n\tfor (const node of graph.nodes) {\n\t\tconst b = boxOf(node.id);\n\t\tif (!b) continue;\n\t\tconst st = resolveMermaidStyles(node.classes, node.styles, graph.classDefs);\n\t\tconst fill = st.fill ?? theme.nodeFill;\n\t\tconst stroke = st.stroke ?? theme.nodeStroke;\n\t\tconst color = st.color ?? theme.text;\n\t\tconst strokeWidth = st['stroke-width']?.replace('px', '') ?? '1.25';\n\t\tconst paint = (kind: string) =>\n\t\t\tkind === 'shape'\n\t\t\t\t? `fill=\"${esc(fill)}\" stroke=\"${esc(stroke)}\" stroke-width=\"${esc(strokeWidth)}\"`\n\t\t\t\t: kind === 'line'\n\t\t\t\t\t? `fill=\"none\" stroke=\"${esc(stroke)}\" stroke-width=\"${esc(strokeWidth)}\"`\n\t\t\t\t\t: kind === 'filled'\n\t\t\t\t\t\t? `fill=\"${esc(stroke)}\" stroke=\"${esc(stroke)}\"`\n\t\t\t\t\t\t: 'fill=\"none\" stroke=\"none\"';\n\t\tout.push(`<g data-node=\"${esc(node.id)}\" transform=\"translate(${n(b.x)} ${n(b.y)})\">`);\n\t\tfor (const path of mermaidShapePaths(node.shape, b.width, b.height)) out.push(`<path d=\"${path.d}\" ${paint(path.paint)}/>`);\n\t\tconst lines = mermaidLabelLines(node.label).filter((l, i, all) => l || all.length === 1);\n\t\tconst members = mermaidNodeMembers(node.data);\n\t\tconst lh = fs * 1.3;\n\t\tif (members.length) {\n\t\t\tlines.forEach((line, i) => out.push(`<text x=\"${n(b.width / 2)}\" y=\"${n(14 + lh * i)}\" text-anchor=\"middle\" dominant-baseline=\"central\" fill=\"${esc(color)}\" font-weight=\"600\">${esc(line)}</text>`));\n\t\t\tconst top = 14 + lh * lines.length - lh / 2 + 4;\n\t\t\tout.push(`<line x1=\"0\" y1=\"${n(top)}\" x2=\"${n(b.width)}\" y2=\"${n(top)}\" stroke=\"${esc(stroke)}\" stroke-width=\"1\"/>`);\n\t\t\tmembers.forEach((m, i) => out.push(`<text x=\"8\" y=\"${n(top + 4 + lh * (i + 0.5))}\" dominant-baseline=\"central\" fill=\"${esc(color)}\" font-size=\"${fs - 1}\">${esc(m)}</text>`));\n\t\t} else {\n\t\t\tconst first = b.height / 2 - ((lines.length - 1) * lh) / 2;\n\t\t\tlines.forEach((line, i) => out.push(`<text x=\"${n(b.width / 2)}\" y=\"${n(first + lh * i)}\" text-anchor=\"middle\" dominant-baseline=\"central\" fill=\"${esc(color)}\">${esc(line)}</text>`));\n\t\t}\n\t\tout.push('</g>');\n\t}\n\tout.push('</g></svg>');\n\treturn { svg: out.join(''), width, height, naturalWidth, naturalHeight };\n}\n\n/** A small preview of a diagram: `mermaidToSvg` fitted inside `maxWidth` × `maxHeight` (default 240 × 160). */\nexport function mermaidThumbnailSvg(doc: MermaidDocument, options: DiagramSvgOptions = {}): DiagramSvg {\n\treturn mermaidToSvg(doc, { maxWidth: 240, maxHeight: 160, padding: 8, ...options });\n}\n\n/** Longest side, in pixels, of a rasterized diagram unless `maxSize` says otherwise. */\nexport const DIAGRAM_RASTER_MAX_SIZE = 2048;\n\nexport interface RasterizeSvgOptions {\n\t/** Size of the SVG in CSS pixels. */\n\twidth: number;\n\theight: number;\n\t/** Pixels per CSS pixel (default 2), reduced so the longest side stays within `maxSize`. */\n\tscale?: number;\n\t/** Longest side of the image in pixels (default 2048). */\n\tmaxSize?: number;\n\t/** Fill behind the drawing (default white; `transparent` keeps the alpha channel). */\n\tbackground?: string;\n\t/** Document to create the canvas and image in (default: the global one). */\n\tdocument?: Document;\n}\n\nexport interface RasterImage {\n\t/** Base64 PNG, without the `data:` prefix. */\n\tdata: string;\n\tmimeType: 'image/png';\n\twidth: number;\n\theight: number;\n}\n\n/** Pixel size of the image for an SVG of `width` × `height`: `scale` × the size, longest side ≤ `maxSize`. */\nexport function rasterSize(width: number, height: number, scale = 2, maxSize = DIAGRAM_RASTER_MAX_SIZE): { width: number; height: number; scale: number } {\n\tif (!(width > 0) || !(height > 0)) throw new Error(`An image needs a positive width and height; got ${width} × ${height}.`);\n\tconst factor = Math.min(scale > 0 ? scale : 1, maxSize / Math.max(width, height));\n\treturn { width: Math.max(1, Math.round(width * factor)), height: Math.max(1, Math.round(height * factor)), scale: factor };\n}\n\nconst NO_DOM =\n\t'Rasterizing needs a browser DOM (document, Image and a 2D canvas). Outside a browser, use the SVG (mermaidToSvg, or render_diagram with format \"svg\") with an SVG renderer.';\n\n/**\n * Turns a standalone SVG (e.g. `mermaidToSvg(...).svg`) into a PNG in the browser: draws it\n * through an `Image` onto a canvas at `scale` (longest side ≤ `maxSize`) over `background`.\n * The SVG must use concrete colours (CSS variables do not apply inside an image). Throws a\n * sentence saying what to do instead when there is no DOM.\n */\nexport async function rasterizeSvg(svg: string, options: RasterizeSvgOptions): Promise<RasterImage> {\n\tconst doc = options.document ?? (globalThis as { document?: Document }).document;\n\tconst ImageCtor = (doc?.defaultView as (Window & typeof globalThis) | null | undefined)?.Image ?? (globalThis as { Image?: typeof Image }).Image;\n\tif (!doc || typeof doc.createElement !== 'function' || !ImageCtor) throw new Error(NO_DOM);\n\tconst size = rasterSize(options.width, options.height, options.scale, options.maxSize);\n\tconst canvas = doc.createElement('canvas');\n\tcanvas.width = size.width;\n\tcanvas.height = size.height;\n\tlet context: CanvasRenderingContext2D | null = null;\n\ttry {\n\t\tcontext = canvas.getContext('2d');\n\t} catch {\n\t\tcontext = null;\n\t}\n\tif (!context) throw new Error(NO_DOM);\n\tconst image = new ImageCtor();\n\tawait new Promise<void>((resolve, reject) => {\n\t\timage.onload = () => resolve();\n\t\timage.onerror = () => reject(new Error('The SVG could not be loaded as an image; check that it is a complete <svg> document with the SVG namespace.'));\n\t\timage.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;\n\t});\n\tconst background = options.background ?? '#ffffff';\n\tif (background !== 'transparent') {\n\t\tcontext.fillStyle = background;\n\t\tcontext.fillRect(0, 0, size.width, size.height);\n\t}\n\tcontext.drawImage(image, 0, 0, size.width, size.height);\n\tconst url = canvas.toDataURL('image/png');\n\tif (!url.startsWith('data:image/png')) throw new Error('This canvas cannot export PNG (toDataURL returned no image).');\n\treturn { data: url.slice(url.indexOf(',') + 1), mimeType: 'image/png', width: size.width, height: size.height };\n}\n\n/** A file name for a diagram: its title in lower case with dashes, else its family, plus the extension. */\nexport function diagramFileName(doc: Pick<MermaidDocument, 'family' | 'graph'>, extension: string): string {\n\tconst base = (doc.graph.title ?? '')\n\t\t.toLowerCase()\n\t\t.replace(/[^\\p{L}\\p{N}]+/gu, '-')\n\t\t.replace(/^-+|-+$/g, '')\n\t\t.slice(0, 60);\n\treturn `${base || `${doc.family}-diagram`}.${extension}`;\n}\n\nexport interface DownloadDiagramPngOptions extends DiagramSvgOptions, Omit<RasterizeSvgOptions, 'width' | 'height'> {\n\t/** File name (default: the diagram title, else `<family>-diagram`, plus `.png`). */\n\tfilename?: string;\n}\n\n/** Renders a diagram to PNG and saves it through a download link. Returns the image and file name. */\nexport async function downloadDiagramPng(doc: MermaidDocument, options: DownloadDiagramPngOptions = {}): Promise<RasterImage & { filename: string }> {\n\tconst drawn = mermaidToSvg(doc, options);\n\tconst background = options.background ?? (options.transparent ? 'transparent' : (options.theme?.background ?? DIAGRAM_SVG_LIGHT_THEME.background));\n\tconst image = await rasterizeSvg(drawn.svg, { ...options, width: drawn.width, height: drawn.height, background });\n\tconst filename = options.filename ?? diagramFileName(doc, 'png');\n\tconst page = options.document ?? document;\n\tconst link = page.createElement('a');\n\tlink.href = `data:image/png;base64,${image.data}`;\n\tlink.download = filename;\n\tpage.body.appendChild(link);\n\tlink.click();\n\tlink.remove();\n\treturn { ...image, filename };\n}\n","/**\n * Mermaid diagrams on the canvas: lays out a `MermaidDocument` (dagre, nested groups,\n * stored `%% @pixerate` positions blended with auto-layout for new nodes), projects it\n * onto canvas nodes and edges, maps canvas drags back to `moveNodes` edits, and streams\n * diagrams that are still being written. Ported from Ursula.\n */\nimport type { EditOp, MermaidDocument, Point } from '../../diagram/types.js';\nimport { ensureDiagramLayout, type DiagramLayout, type DiagramLayoutOptions } from './layout.js';\nimport { mermaidFlowEdges, mermaidFlowNodes, type MermaidFlowEdge, type MermaidFlowNode } from './toFlow.js';\n\nexport * from './layout.js';\nexport * from './toFlow.js';\nexport * from './view.js';\nexport * from './stream.js';\nexport * from './scene.js';\nexport * from './families/index.js';\n\nexport interface MermaidCanvasOptions extends DiagramLayoutOptions {\n\t/** Nodes can be dragged, connected and selected (default false). */\n\teditable?: boolean;\n\t/** A layout to draw instead of computing one (e.g. a tween frame). */\n\tlayout?: DiagramLayout;\n}\n\nexport interface MermaidCanvas {\n\tnodes: MermaidFlowNode[];\n\tedges: MermaidFlowEdge[];\n\t/** Absolute positions and sizes of every node and group. */\n\tlayout: DiagramLayout;\n}\n\n/**\n * Lays out a Mermaid document and returns canvas nodes (groups first, child positions\n * relative to their parent as React Flow / Svelte Flow expect) and edges.\n */\nexport function mermaidToCanvas(doc: MermaidDocument, options: MermaidCanvasOptions = {}): MermaidCanvas {\n\tconst layout = options.layout ?? ensureDiagramLayout(doc.graph, doc.meta, { family: doc.family, ...options });\n\treturn {\n\t\tnodes: mermaidFlowNodes(doc.graph, layout, { editable: options.editable }),\n\t\tedges: mermaidFlowEdges(doc.graph, { editable: options.editable }),\n\t\tlayout\n\t};\n}\n\nexport interface MermaidMovesOptions {\n\t/**\n\t * Positions already stored in the document (`doc.meta.layout`). Nodes that are laid out\n\t * automatically and not yet stored are pinned where they are, so moving one node does\n\t * not shift the others (new nodes are blended in around stored ones).\n\t */\n\tstored?: Readonly<Record<string, Point>>;\n}\n\n/**\n * The `moveNodes` edit for canvas nodes that were dragged: converts their positions\n * (relative to their parent) back to absolute coordinates. Returns `null` when nothing\n * moved by at least half a pixel.\n */\nexport function mermaidMovesFromCanvas(\n\tlayout: DiagramLayout,\n\tmoved: ReadonlyArray<{ id: string; position: { x: number; y: number }; parentId?: string }>,\n\tall: ReadonlyArray<{ id: string; position: { x: number; y: number }; parentId?: string }> = moved,\n\toptions: MermaidMovesOptions = {}\n): Extract<EditOp, { type: 'moveNodes' }> | null {\n\t// The moved nodes win over their stale copies in `all`.\n\tconst byId = new Map([...all, ...moved].map((n) => [n.id, n]));\n\tconst absolute = (id: string, seen = new Set<string>()): Point => {\n\t\tconst node = byId.get(id);\n\t\tif (!node) return layout.positions[id] ?? [0, 0];\n\t\tif (!node.parentId || seen.has(id)) return [node.position.x, node.position.y];\n\t\tseen.add(id);\n\t\tconst [px, py] = absolute(node.parentId, seen);\n\t\treturn [px + node.position.x, py + node.position.y];\n\t};\n\tconst moves = moved\n\t\t.map((n) => ({ id: n.id, position: absolute(n.id) }))\n\t\t.filter(({ id, position: [x, y] }) => {\n\t\t\tconst before = layout.positions[id];\n\t\t\treturn !before || Math.abs(before[0] - x) >= 0.5 || Math.abs(before[1] - y) >= 0.5;\n\t\t});\n\t// A dragged group carries its contents, so store their new absolute positions too.\n\tconst movedIds = new Set(moves.map((m) => m.id));\n\tfor (const n of all) {\n\t\tif (movedIds.has(n.id)) continue;\n\t\tlet parent = n.parentId;\n\t\twhile (parent && !movedIds.has(parent)) parent = byId.get(parent)?.parentId;\n\t\tif (parent) moves.push({ id: n.id, position: absolute(n.id) });\n\t}\n\tif (!moves.length) return null;\n\tif (options.stored) {\n\t\tconst taken = new Set(moves.map((m) => m.id));\n\t\tfor (const [id, position] of Object.entries(layout.positions)) {\n\t\t\tif (!taken.has(id) && !options.stored[id]) moves.push({ id, position });\n\t\t}\n\t}\n\treturn { type: 'moveNodes', moves };\n}\nexport * from './shapes.js';\nexport * from './export.js';\n","/**\n * UI-agnostic agent presence trajectory, inspection anchor, and cursor rotation math.\n */\n\nexport const DEFAULT_CURSOR_INACTIVITY_MS = 12000;\n\nexport interface CanvasAgentCursor {\n\tid: string;\n\tuserId?: string;\n\tx: number;\n\ty: number;\n\tusername?: string;\n\tavatarUrl?: string;\n\tcolor?: string;\n\tisAgent?: boolean;\n\tisServiceAccount?: boolean;\n\trole?: string;\n\tstatusText?: string;\n\tmessage?: string;\n\tnote?: string;\n\tlastUpdate?: number;\n\ttimeoutMs?: number;\n\ttransitionDurationMs?: number;\n\tcustomEase?: string;\n\ttargetNodeId?: string;\n\ttargetNodeCenter?: { x: number; y: number } | null;\n\tisModalOpen?: boolean;\n\tisClicking?: boolean;\n}\n\n/**\n * Deterministic string hash function used to derive consistent per-agent / per-node variances.\n */\nexport function hashString(str: string): number {\n\tlet hash = 0;\n\tfor (let i = 0; i < str.length; i++) {\n\t\thash = (hash << 5) - hash + str.charCodeAt(i);\n\t\thash |= 0;\n\t}\n\treturn Math.abs(hash);\n}\n\nexport type CardInspectionZone = 'header' | 'action' | 'body' | 'footer';\n\nexport const CARD_INSPECTION_ZONES: Record<CardInspectionZone, { x: number; y: number }> = {\n\theader: { x: 36, y: 24 },\n\taction: { x: 180, y: 28 },\n\tbody: { x: 60, y: 84 },\n\tfooter: { x: 165, y: 122 }\n};\n\n/**\n * Computes a varied inspection anchor position on a node/card (default width ~250px, height ~160px).\n * Generates distinct natural resting spots so agents do not appear at the exact same coordinates.\n */\nexport function getAgentCardAnchor(\n\tagentId: string = 'agent',\n\tnodeId: string = 'node',\n\tzone: CardInspectionZone | number = 0,\n\toptions?: { randomJitter?: boolean; width?: number; height?: number }\n): { x: number; y: number; zoneName: CardInspectionZone } {\n\tconst zoneNames: CardInspectionZone[] = ['header', 'action', 'body', 'footer'];\n\tconst zoneName =\n\t\ttypeof zone === 'string' ? zone : zoneNames[Math.abs(Math.floor(zone)) % zoneNames.length];\n\n\tconst base = CARD_INSPECTION_ZONES[zoneName];\n\tconst seed = hashString(`${agentId}:${nodeId}:${zoneName}`);\n\n\t// Deterministic pseudo-random base offset within zone (-8px to +8px in X, -4px to +4px in Y)\n\tconst offsetX = (seed % 17) - 8;\n\tconst offsetY = ((seed >> 4) % 9) - 4;\n\n\t// Organic micro-randomness (live jitter ±12px in X, ±8px in Y when opted in)\n\tconst jitterX = options?.randomJitter ? (Math.random() - 0.5) * 24 : 0;\n\tconst jitterY = options?.randomJitter ? (Math.random() - 0.5) * 16 : 0;\n\n\tconst maxX = options?.width ? Math.max(24, options.width - 20) : 230;\n\tconst maxY = options?.height ? Math.max(20, options.height - 15) : 145;\n\n\treturn {\n\t\tx: Math.max(16, Math.min(maxX, Math.round(base.x + offsetX + jitterX))),\n\t\ty: Math.max(14, Math.min(maxY, Math.round(base.y + offsetY + jitterY))),\n\t\tzoneName\n\t};\n}\n\nexport interface CurvedPathOptions {\n\tarcHeight?: number; // negative lifts upward in screen coordinates, e.g. -30px\n\twobble?: number; // subtle lateral sway, e.g. 3px\n}\n\n/**\n * Calculates coordinates along a natural curved trajectory with parabolic lift\n * and optional lateral sway, replacing robotic straight linear movement.\n */\nexport function calculateCurvedPath(\n\tfromX: number,\n\tfromY: number,\n\ttoX: number,\n\ttoY: number,\n\tprogress: number,\n\toptions?: CurvedPathOptions\n): { x: number; y: number } {\n\tconst p = Math.max(0, Math.min(1, progress));\n\tconst arcHeight = options?.arcHeight ?? -30;\n\tconst wobble = options?.wobble ?? 0;\n\n\t// Parabolic lift peak at mid-flight (p = 0.5)\n\tconst arc = Math.sin(p * Math.PI) * arcHeight;\n\t// Natural full-cycle lateral sway\n\tconst sway = Math.sin(p * Math.PI * 2) * wobble;\n\n\tconst currentX = fromX + (toX - fromX) * p + sway;\n\tconst currentY = fromY + (toY - fromY) * p + arc;\n\n\treturn { x: currentX, y: currentY };\n}\n\n/**\n * Computes natural drag movement timing, parabolic arc amplitude, and grip offset\n * based on distance traveled and a deterministic seed.\n */\nexport function getNaturalMovementTiming(\n\tdistance: number,\n\tseedStr: string = 'drag'\n): {\n\tduration: number;\n\tarcHeight: number;\n\tgrabOffset: { x: number; y: number };\n} {\n\tconst seed = hashString(seedStr);\n\tconst absDist = Math.abs(distance);\n\n\t// Scale duration with distance: clamped between 500ms and 1000ms\n\tconst distDuration = 560 + Math.min(340, (absDist / 1000) * 300);\n\tconst variance = (seed % 120) - 60; // -60ms to +60ms\n\tconst duration = Math.round(Math.max(500, Math.min(1000, distDuration + variance)));\n\n\t// Arc height: larger distance lifts slightly higher (-24px to -44px)\n\tconst baseArc = -24 - Math.min(16, (absDist / 1000) * 14);\n\tconst arcVariance = ((seed >> 3) % 9) - 4; // -4px to +4px\n\tconst arcHeight = Math.round(baseArc + arcVariance);\n\n\t// Varied grab offset across the card header\n\tconst grabX = 24 + (seed % 24);\n\tconst grabY = 12 + ((seed >> 2) % 14);\n\n\treturn {\n\t\tduration,\n\t\tarcHeight,\n\t\tgrabOffset: { x: grabX, y: grabY }\n\t};\n}\n\n/**\n * Computes variable CSS transition duration in ms based on distance and seed.\n */\nexport function getCursorTransitionDuration(\n\tdistance: number,\n\tseedStr: string = 'cursor',\n\toptions?: { naturalVariance?: boolean }\n): number {\n\tconst seed = hashString(seedStr);\n\tconst jitter = (seed % 60) - 30; // -30ms to +30ms\n\t// Natural timing variance (±15% when opted in)\n\tconst variance = options?.naturalVariance ? 0.85 + Math.random() * 0.3 : 1;\n\n\tlet baseDuration = 560;\n\tif (distance < 60) {\n\t\tbaseDuration = 340;\n\t} else if (distance < 300) {\n\t\tbaseDuration = 440;\n\t}\n\tconst computed = Math.round((baseDuration + jitter) * variance);\n\treturn Math.max(300, Math.min(650, computed));\n}\n\n/**\n * Calculates the SVG rotation angle for aligning a presence cursor arrow\n * toward the center of a target node.\n */\nexport function calculateCursorArrowRotation(\n\tcursor: { x: number; y: number; isModalOpen?: boolean },\n\ttargetCenter?: { x: number; y: number } | null,\n\tisAgentOrServiceAccount: boolean = false,\n\tisModalOpen?: boolean,\n\tcurrentRotation: number = 0\n): { shouldPoint: boolean; rotationDeg: number } {\n\tconst hasModal = isModalOpen ?? Boolean(cursor.isModalOpen);\n\tconst shouldPoint = Boolean(targetCenter && (isAgentOrServiceAccount || hasModal));\n\n\tif (!shouldPoint || !targetCenter) {\n\t\treturn { shouldPoint: false, rotationDeg: 0 };\n\t}\n\n\tconst dx = targetCenter.x - cursor.x;\n\tconst dy = targetCenter.y - cursor.y;\n\tconst dist = Math.hypot(dx, dy);\n\n\tif (dist < 2) {\n\t\treturn { shouldPoint: true, rotationDeg: currentRotation };\n\t}\n\n\tconst angleDeg = Math.atan2(dy, dx) * (180 / Math.PI);\n\t// Default unrotated SVG pointer tip faces -135deg (up-and-left).\n\t// Rotating by (angleDeg + 135) directs the tip directly toward the target.\n\tconst rawTargetDeg = angleDeg + 135;\n\n\t// Shortest rotation unwrapping\n\tconst diff = ((((rawTargetDeg - currentRotation + 180) % 360) + 360) % 360) - 180;\n\treturn {\n\t\tshouldPoint: true,\n\t\trotationDeg: currentRotation + diff\n\t};\n}\n\n/**\n * Sanitizes an agent name by removing any leading '@' prefix(es) and trimming whitespace.\n */\nexport function formatAgentCursorName(nameOrUsername?: string | null): string {\n\tif (!nameOrUsername) return 'Agent';\n\tconst cleaned = nameOrUsername.trim().replace(/^@+/, '').trim();\n\treturn cleaned || 'Agent';\n}\n\n/**\n * Determines whether a given presence cursor represents an autonomous agent.\n */\nexport function isAgentCursor(cursor: Partial<CanvasAgentCursor>): boolean {\n\tif (cursor.isAgent) return true;\n\tif (cursor.userId?.startsWith('agent_') || cursor.userId?.startsWith('agent-')) return true;\n\tif (cursor.id?.startsWith('agent-') || cursor.id?.startsWith('agent_')) return true;\n\tif (cursor.username?.startsWith('@agent_') || cursor.username?.startsWith('agent_')) return true;\n\n\tconst roleLower = cursor.role?.toLowerCase() || '';\n\tif (\n\t\troleLower.includes('agent') ||\n\t\troleLower.includes('specialist') ||\n\t\troleLower === 'planner' ||\n\t\troleLower === 'coordinator' ||\n\t\troleLower === 'executor' ||\n\t\troleLower === 'validator' ||\n\t\troleLower === 'supervisor'\n\t) {\n\t\treturn true;\n\t}\n\n\tconst usernameLower = (cursor.username || '').toLowerCase();\n\tif (usernameLower.includes('agent') || usernameLower.includes('specialist')) {\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n","import {\n\tcreateCaptureToolProvider,\n\tunionCaptureRects,\n\ttype CaptureRect,\n\ttype CaptureSurface,\n\ttype CaptureToolProviderOptions,\n\ttype CapturedImage\n} from '../tools/capture.js';\nimport {\n\tCAPTURE_EXCLUDE_SELECTOR,\n\tCAPTURE_OVERLAY_SELECTORS,\n\trasterizeElement,\n\ttype RasterizeElementOptions\n} from '../tools/dom-capture.js';\nimport type { AgentToolProvider } from '../tools/registry.js';\nimport type { CanvasNode, Rect } from './types.js';\n\n/** A flow viewport: screen = flow × zoom + (x, y). */\nexport interface CanvasCaptureViewport {\n\tx: number;\n\ty: number;\n\tzoom: number;\n}\n\nexport interface CanvasCaptureSurfaceOptions<TNode extends CanvasNode = CanvasNode> {\n\t/** The flow's root element (`.react-flow` / `.svelte-flow`), or a getter (re-read on every call). */\n\telement: HTMLElement | null | undefined | (() => HTMLElement | null | undefined);\n\t/** The live viewport (e.g. `useReactFlow().getViewport` / `useSvelteFlow().getViewport`). */\n\tgetViewport: () => CanvasCaptureViewport;\n\t/** The live nodes (e.g. `useReactFlow().getNodes`). */\n\tgetNodes: () => readonly TNode[];\n\t/** Absolute bounds of a node in flow coordinates (default: its position plus its parents', and its measured size). */\n\tgetNodeRect?: (node: TNode, nodes: readonly TNode[]) => Rect | null;\n\t/** Selector of the transformed viewport element inside `element`. */\n\tviewportSelector?: string;\n\t/** Never captured: panels, controls, minimap (default `CANVAS_CAPTURE_CHROME_SELECTORS`). */\n\tchromeSelectors?: readonly string[];\n\t/** Left out unless `includeOverlays` (default `CANVAS_CAPTURE_OVERLAY_SELECTORS`). */\n\toverlaySelectors?: readonly string[];\n\t/** Fill behind the canvas (default: the root element's computed background). */\n\tbackground?: string;\n\t/** Largest output pixels per canvas unit (default 2). */\n\tmaxScale?: number;\n\t/** Swaps the drawing step (tests, or a library such as html-to-image). */\n\trasterize?: (element: HTMLElement, options: RasterizeElementOptions) => Promise<CapturedImage>;\n}\n\n/** The transformed viewport inside React Flow and Svelte Flow. */\nexport const CANVAS_CAPTURE_VIEWPORT_SELECTOR = '.xyflow__viewport, .react-flow__viewport, .svelte-flow__viewport';\n\n/** UI chrome that is never part of a canvas capture. */\nexport const CANVAS_CAPTURE_CHROME_SELECTORS: readonly string[] = [\n\tCAPTURE_EXCLUDE_SELECTOR,\n\t'.react-flow__panel',\n\t'.svelte-flow__panel',\n\t'.react-flow__minimap',\n\t'.svelte-flow__minimap',\n\t'.react-flow__controls',\n\t'.svelte-flow__controls',\n\t'.react-flow__attribution',\n\t'.svelte-flow__attribution'\n];\n\n/** Transient overlays: presence cursors, selection boxes and resize handles. */\nexport const CANVAS_CAPTURE_OVERLAY_SELECTORS: readonly string[] = [\n\t...CAPTURE_OVERLAY_SELECTORS,\n\t'.react-flow__nodesselection',\n\t'.svelte-flow__nodesselection',\n\t'.react-flow__selection',\n\t'.svelte-flow__selection',\n\t'.react-flow__resize-control',\n\t'.svelte-flow__resize-control'\n];\n\nconst size = (node: CanvasNode) => ({\n\twidth: node.measured?.width ?? node.width ?? 0,\n\theight: node.measured?.height ?? node.height ?? 0\n});\n\n/** A node's bounds in flow coordinates: its position plus every parent's, and its measured size. */\nexport function canvasNodeCaptureRect<TNode extends CanvasNode>(node: TNode, nodes: readonly TNode[]): Rect | null {\n\tif (node.hidden) return null;\n\tlet x = node.position.x;\n\tlet y = node.position.y;\n\tconst seen = new Set([node.id]);\n\tlet parentId = node.parentId ?? (node as { parentNode?: string }).parentNode;\n\twhile (parentId && !seen.has(parentId)) {\n\t\tseen.add(parentId);\n\t\tconst parent = nodes.find((n) => n.id === parentId);\n\t\tif (!parent) break;\n\t\tx += parent.position.x;\n\t\ty += parent.position.y;\n\t\tparentId = parent.parentId ?? (parent as { parentNode?: string }).parentNode;\n\t}\n\tconst { width, height } = size(node);\n\treturn { x, y, width, height };\n}\n\n/**\n * A capture surface for a React Flow / Svelte Flow canvas. Units are flow\n * coordinates (node positions). `viewport` draws the canvas as it is on\n * screen (with its background); other regions re-frame the nodes and edges\n * onto a plain background, wherever they are scrolled to. Panels, controls\n * and the minimap are never drawn; selection boxes and presence cursors only\n * with `includeOverlays`.\n */\nexport function createCanvasCaptureSurface<TNode extends CanvasNode = CanvasNode>(\n\toptions: CanvasCaptureSurfaceOptions<TNode>\n): CaptureSurface {\n\tconst getElement = () => (typeof options.element === 'function' ? options.element() : options.element) ?? null;\n\tconst nodeRect = (node: TNode, nodes: readonly TNode[]) => (options.getNodeRect ?? canvasNodeCaptureRect)(node, nodes);\n\tconst rectsOf = (nodes: readonly TNode[], pick: (n: TNode) => boolean) =>\n\t\tnodes.filter(pick).map((n) => nodeRect(n, nodes)).filter((r): r is Rect => !!r && r.width > 0 && r.height > 0);\n\tconst viewportSelector = options.viewportSelector ?? CANVAS_CAPTURE_VIEWPORT_SELECTOR;\n\n\tconst viewport = (): CaptureRect | null => {\n\t\tconst el = getElement();\n\t\tconst vp = options.getViewport();\n\t\tif (!el || !el.clientWidth || !el.clientHeight || !(vp.zoom > 0)) return null;\n\t\treturn { x: -vp.x / vp.zoom, y: -vp.y / vp.zoom, width: el.clientWidth / vp.zoom, height: el.clientHeight / vp.zoom };\n\t};\n\n\treturn {\n\t\tunits: 'canvas units',\n\t\tviewport,\n\t\tcontent: () => unionCaptureRects(rectsOf(options.getNodes(), () => true)),\n\t\tselection: () => {\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst selected = (n: TNode) => !!n.selected && !n.hidden;\n\t\t\treturn { rect: unionCaptureRects(rectsOf(nodes, selected)), ids: nodes.filter(selected).map((n) => n.id) };\n\t\t},\n\t\titems: {\n\t\t\tregion: 'nodes',\n\t\t\tnoun: 'node',\n\t\t\tsource: 'the canvas state',\n\t\t\tbounds: (ids) => {\n\t\t\t\tconst nodes = options.getNodes();\n\t\t\t\tconst missing = ids.filter((id) => !nodes.some((n) => n.id === id));\n\t\t\t\treturn { rect: unionCaptureRects(rectsOf(nodes, (n) => ids.includes(n.id))), missing };\n\t\t\t}\n\t\t},\n\t\toverlays: 'selection boxes, resize handles and agent cursors',\n\t\tmaxScale: options.maxScale ?? 2,\n\t\trender: (request) => {\n\t\t\tconst el = getElement();\n\t\t\tif (!el || !el.isConnected) throw new Error('The canvas is not mounted, so there is nothing to capture.');\n\t\t\tconst rasterize = options.rasterize ?? rasterizeElement;\n\t\t\tconst exclude = [\n\t\t\t\t...(options.chromeSelectors ?? CANVAS_CAPTURE_CHROME_SELECTORS),\n\t\t\t\t...(request.includeOverlays ? [] : (options.overlaySelectors ?? CANVAS_CAPTURE_OVERLAY_SELECTORS))\n\t\t\t];\n\t\t\tconst common = { pixelWidth: request.width, pixelHeight: request.height, format: request.format, quality: request.quality, background: options.background };\n\t\t\tif (request.region === 'viewport') {\n\t\t\t\treturn rasterize(el, { ...common, width: el.clientWidth, height: el.clientHeight, exclude });\n\t\t\t}\n\t\t\t// Re-frame: draw only the viewport's subtree with a transform that puts the rect at the top left.\n\t\t\tconst zoom = request.scale;\n\t\t\treturn rasterize(el, {\n\t\t\t\t...common,\n\t\t\t\twidth: request.width,\n\t\t\t\theight: request.height,\n\t\t\t\texclude,\n\t\t\t\tprepare: (clone) => {\n\t\t\t\t\tconst vpEl = clone.querySelector<HTMLElement>(viewportSelector);\n\t\t\t\t\tif (!vpEl) throw new Error(`The canvas has no viewport element (${viewportSelector}); pass viewportSelector.`);\n\t\t\t\t\tvpEl.style.transform = `translate(${-request.rect.x * zoom}px, ${-request.rect.y * zoom}px) scale(${zoom})`;\n\t\t\t\t\tvpEl.style.transformOrigin = '0 0';\n\t\t\t\t\t// Background patterns and screen-space layers follow the live viewport, not this frame.\n\t\t\t\t\tfor (let node: HTMLElement | null = vpEl; node && node !== clone; node = node.parentElement) {\n\t\t\t\t\t\tfor (const sibling of Array.from(node.parentElement?.children ?? [])) {\n\t\t\t\t\t\t\tif (sibling !== node) sibling.remove();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n}\n\nexport interface CanvasCaptureToolProviderOptions<TNode extends CanvasNode = CanvasNode>\n\textends CanvasCaptureSurfaceOptions<TNode>,\n\t\tCaptureToolProviderOptions {}\n\n/**\n * `capture_view` for a React Flow / Svelte Flow canvas: a screenshot of the\n * viewport, all content, the selection, some nodes by id, or a rect in\n * canvas units. Default id `canvas-capture`.\n */\nexport function createCanvasCaptureToolProvider<TNode extends CanvasNode = CanvasNode>(\n\toptions: CanvasCaptureToolProviderOptions<TNode>\n): AgentToolProvider {\n\treturn createCaptureToolProvider(createCanvasCaptureSurface(options), {\n\t\tsubject: 'the canvas',\n\t\t...options,\n\t\tid: options.id ?? 'canvas-capture'\n\t});\n}\n","/**\n * Provider effects on the canvas (see `@pixerate/editor/effects`): effect fills and overlays on\n * shape nodes (`data.shape.effect`), and transition effects into Scenes (`CanvasScene.transition`).\n *\n * The canvas has no single controller, so `createCanvasEffectsController` works like the other\n * canvas controllers: it reads and writes nodes through `getNodes` / `setNodes`, and records each\n * change as one undoable command on an optional `history`. The framework bindings draw the\n * effects (an animated canvas inside the node) and play scene transitions.\n */\n\nimport { EffectsSlot, resolveEffectValues } from '../effects/helpers.js';\nimport { EffectUnsupportedError } from '../effects/errors.js';\nimport type { EffectDescription, EffectProvider, EffectUse, EffectValue } from '../effects/types.js';\nimport type { Command } from '../history/types.js';\nimport type { CanvasSceneTransition, CanvasScenesStore } from './scenes.js';\nimport { isShapeDescription, type CanvasShapeEffect, type ShapeDescription } from './shapeGeometry.js';\nimport type { CanvasNode } from './types.js';\n\n/** The uses each place takes. */\nexport const CANVAS_EFFECT_PLACES = {\n\t/** Paints a shape's inside. */\n\tfill: ['generator', 'interactive'],\n\t/** Draws over a node's box. */\n\toverlay: ['overlay', 'title', 'interactive', 'generator'],\n\ttransition: ['transition']\n} as const satisfies Record<string, readonly EffectUse[]>;\n\nexport type CanvasEffectPlace = keyof typeof CANVAS_EFFECT_PLACES;\n\n/** The shape effect of a node, if it has one. */\nexport function canvasNodeEffect(node: CanvasNode): CanvasShapeEffect | undefined {\n\tconst shape = (node.data as { shape?: unknown } | undefined)?.shape;\n\treturn isShapeDescription(shape) ? (shape as ShapeDescription).effect : undefined;\n}\n\nexport interface CanvasEffectsControllerOptions<TNode extends CanvasNode = CanvasNode> {\n\tgetNodes: () => TNode[];\n\tsetNodes: (nodes: TNode[]) => void;\n\t/** When set, every change is one undoable command. */\n\thistory?: { execute(command: Command): void };\n\t/** The Scenes store, for transitions between scenes. */\n\tscenes?: CanvasScenesStore;\n\t/** An effects provider (optional; set or change it later with `setEffectsProvider`). */\n\teffects?: EffectProvider | null;\n}\n\n/** Headless effects for a canvas (see the module comment). */\nexport function createCanvasEffectsController<TNode extends CanvasNode = CanvasNode>(options: CanvasEffectsControllerOptions<TNode>) {\n\tconst slot = new EffectsSlot();\n\tif (options.effects) slot.set(options.effects);\n\n\tconst describe = async (effect: string, place: CanvasEffectPlace): Promise<EffectDescription> => {\n\t\tconst d = await slot.require().describe(effect);\n\t\tconst allowed: readonly EffectUse[] = CANVAS_EFFECT_PLACES[place];\n\t\tif (d.kind === 'motion' || !d.uses.some((u) => allowed.includes(u))) {\n\t\t\tconst where = place === 'transition' ? 'a scene transition' : place === 'fill' ? 'a fill' : 'an overlay';\n\t\t\tthrow new EffectUnsupportedError(`\"${d.id}\" (${d.name}) is a ${d.uses.join(' / ')} effect and cannot be ${where}, which takes ${allowed.join(' or ')} effects; search_effects with use \"${allowed[0]}\" lists ones that fit.`, d.id);\n\t\t}\n\t\treturn d;\n\t};\n\n\tconst shapeNode = (id: string): { node: TNode; shape: ShapeDescription } => {\n\t\tconst node = options.getNodes().find((n) => n.id === id);\n\t\tif (!node) throw new Error(`There is no node \"${id}\"; the nodes are ${options.getNodes().map((n) => `\"${n.id}\"`).slice(0, 20).join(', ') || 'none'}.`);\n\t\tconst shape = (node.data as { shape?: unknown } | undefined)?.shape;\n\t\tif (!isShapeDescription(shape)) throw new Error(`Node \"${id}\" is not a shape; effects draw on shape nodes (data.shape).`);\n\t\treturn { node, shape };\n\t};\n\n\tconst commit = (label: string, next: TNode[]) => {\n\t\tconst before = options.getNodes();\n\t\tif (options.history) options.history.execute({ name: label, execute: () => options.setNodes(next), undo: () => options.setNodes(before), redo: () => options.setNodes(next) } as Command);\n\t\telse options.setNodes(next);\n\t};\n\n\tconst withEffect = (id: string, effect: CanvasShapeEffect | undefined): TNode[] =>\n\t\toptions.getNodes().map((n) => {\n\t\t\tif (n.id !== id) return n;\n\t\t\tconst shape = { ...((n.data as { shape: ShapeDescription }).shape) };\n\t\t\tif (effect) shape.effect = effect;\n\t\t\telse delete shape.effect;\n\t\t\treturn { ...n, data: { ...(n.data as object), shape } } as TNode;\n\t\t});\n\n\tconst mergeValues = (d: EffectDescription, was: Record<string, unknown>, values: Record<string, EffectValue | null>) => {\n\t\tconst base = { ...(was as Record<string, EffectValue>) };\n\t\tconst given: Record<string, EffectValue> = {};\n\t\tfor (const [k, v] of Object.entries(values)) {\n\t\t\tif (v !== null) given[k] = v;\n\t\t\telse if (k in d.controls.properties) base[k] = d.defaults[k];\n\t\t\telse throw new Error(`\"${k}\" is not a control of \"${d.id}\"; its controls are ${Object.keys(d.controls.properties).join(', ')}.`);\n\t\t}\n\t\treturn { ...base, ...resolveEffectValues({ ...d, defaults: base }, given) };\n\t};\n\n\treturn {\n\t\t/** The provider slot. */\n\t\teffects: slot,\n\t\tget effectsProvider(): EffectProvider | null {\n\t\t\treturn slot.provider;\n\t\t},\n\t\t/** Registers (or with `null` removes) the effects provider. */\n\t\tsetEffectsProvider(provider: EffectProvider | null) {\n\t\t\tslot.set(provider);\n\t\t},\n\t\t/** Registers `provider` and returns a function that removes it again. */\n\t\tuseEffects(provider: EffectProvider) {\n\t\t\treturn slot.use(provider);\n\t\t},\n\t\t/** The shape nodes that carry effects (or one node's). */\n\t\tlist(nodeId?: string): { nodeId: string; effect: CanvasShapeEffect }[] {\n\t\t\treturn options\n\t\t\t\t.getNodes()\n\t\t\t\t.filter((n) => !nodeId || n.id === nodeId)\n\t\t\t\t.map((n) => ({ nodeId: n.id, effect: canvasNodeEffect(n) }))\n\t\t\t\t.filter((e): e is { nodeId: string; effect: CanvasShapeEffect } => !!e.effect);\n\t\t},\n\t\t/**\n\t\t * Draws a provider effect on a shape node: `fill` paints its inside (generators), `overlay`\n\t\t * draws over its box (overlays, titles, interactive effects). Replaces any effect it had. One\n\t\t * undoable step. Returns the effect.\n\t\t */\n\t\tasync applyNodeEffect(nodeId: string, effect: string, opts: { mode?: 'fill' | 'overlay'; values?: Record<string, EffectValue>; opacity?: number } = {}): Promise<CanvasShapeEffect> {\n\t\t\tshapeNode(nodeId);\n\t\t\tconst mode = opts.mode ?? 'fill';\n\t\t\tconst d = await describe(effect, mode);\n\t\t\tif (opts.opacity !== undefined && !(opts.opacity >= 0 && opts.opacity <= 1)) throw new Error('opacity must be from 0 to 1.');\n\t\t\tconst next: CanvasShapeEffect = { effect: d.id, provider: slot.require().id, name: d.name, values: resolveEffectValues(d, opts.values) as CanvasShapeEffect['values'], mode, ...(opts.opacity !== undefined && opts.opacity !== 1 ? { opacity: opts.opacity } : {}) };\n\t\t\tcommit(`Add effect ${d.name}`, withEffect(nodeId, next));\n\t\t\treturn next;\n\t\t},\n\t\t/** Changes a node's effect: `values` merged (`null` resets), `mode`, `opacity`, `playing`. One undoable step. */\n\t\tasync updateNodeEffect(nodeId: string, changes: { values?: Record<string, EffectValue | null>; mode?: 'fill' | 'overlay'; opacity?: number; playing?: boolean }): Promise<CanvasShapeEffect> {\n\t\t\tconst { shape } = shapeNode(nodeId);\n\t\t\tconst was = shape.effect;\n\t\t\tif (!was) throw new Error(`Node \"${nodeId}\" has no effect; add one with apply_effect.`);\n\t\t\tconst next: CanvasShapeEffect = { ...was };\n\t\t\tif (changes.values || changes.mode) {\n\t\t\t\tconst d = await describe(was.effect, changes.mode ?? was.mode);\n\t\t\t\tif (changes.values) next.values = mergeValues(d, was.values, changes.values) as CanvasShapeEffect['values'];\n\t\t\t\tif (changes.mode) next.mode = changes.mode;\n\t\t\t} else slot.require();\n\t\t\tif (changes.opacity !== undefined) {\n\t\t\t\tif (!(changes.opacity >= 0 && changes.opacity <= 1)) throw new Error('opacity must be from 0 to 1.');\n\t\t\t\tif (changes.opacity === 1) delete next.opacity;\n\t\t\t\telse next.opacity = changes.opacity;\n\t\t\t}\n\t\t\tif (changes.playing !== undefined) {\n\t\t\t\tif (changes.playing) delete next.playing;\n\t\t\t\telse next.playing = false;\n\t\t\t}\n\t\t\tcommit(`Change effect ${was.name ?? was.effect}`, withEffect(nodeId, next));\n\t\t\treturn next;\n\t\t},\n\t\t/** Removes a node's effect (works without a provider). One undoable step. */\n\t\tremoveNodeEffect(nodeId: string): CanvasShapeEffect {\n\t\t\tconst { shape } = shapeNode(nodeId);\n\t\t\tif (!shape.effect) throw new Error(`Node \"${nodeId}\" has no effect.`);\n\t\t\tcommit(`Remove effect ${shape.effect.name ?? shape.effect.effect}`, withEffect(nodeId, undefined));\n\t\t\treturn shape.effect;\n\t\t},\n\t\t/** The scenes, in order (empty without a Scenes store). */\n\t\tscenes() {\n\t\t\treturn options.scenes?.getState().scenes ?? [];\n\t\t},\n\t\t/** The transition into a scene, if it has one. */\n\t\tsceneTransition(sceneId: string): CanvasSceneTransition | undefined {\n\t\t\treturn options.scenes?.getScene(sceneId)?.transition;\n\t\t},\n\t\t/**\n\t\t * Sets (or with `effect` null removes) the provider transition played when the canvas moves to\n\t\t * a scene. Returns the transition.\n\t\t */\n\t\tasync setSceneTransition(sceneId: string, effect: string | null, opts: { values?: Record<string, EffectValue>; duration?: number } = {}): Promise<CanvasSceneTransition | null> {\n\t\t\tconst scenes = options.scenes;\n\t\t\tif (!scenes) throw new Error('This canvas has no Scenes store.');\n\t\t\tif (!scenes.getScene(sceneId)) throw new Error(`There is no scene \"${sceneId}\"; the scenes are ${scenes.getState().scenes.map((s) => `\"${s.id}\"`).join(', ') || 'none'}.`);\n\t\t\tif (effect === null) {\n\t\t\t\tscenes.update(sceneId, { transition: undefined });\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst d = await describe(effect, 'transition');\n\t\t\tconst duration = opts.duration ?? d.duration ?? 600;\n\t\t\tif (!(duration > 0)) throw new Error('duration must be a number of ms above 0.');\n\t\t\tconst transition: CanvasSceneTransition = { effect: d.id, provider: slot.require().id, name: d.name, values: resolveEffectValues(d, opts.values) as CanvasSceneTransition['values'], duration };\n\t\t\tscenes.update(sceneId, { transition });\n\t\t\treturn transition;\n\t\t}\n\t};\n}\n\nexport type CanvasEffectsController<TNode extends CanvasNode = CanvasNode> = ReturnType<typeof createCanvasEffectsController<TNode>>;\n","/**\n * Plays provider effects on a canvas element, for the framework bindings: a node's effect drawn\n * frame by frame (`runCanvasNodeEffect`), and a scene transition between two pictures\n * (`playCanvasSceneTransition`). Renders go through an `EffectRendererPool`, so a page full of\n * effects stays within the browser's WebGL context limit.\n */\n\nimport { EffectRendererPool } from '../effects/pool.js';\nimport type { EffectPixels, EffectProvider } from '../effects/types.js';\nimport type { CanvasSceneTransition } from './scenes.js';\nimport type { CanvasShapeEffect } from './shapeGeometry.js';\nimport { CAPTURE_OVERLAY_SELECTORS, rasterizeElement } from '../tools/dom-capture.js';\n\n/** Where frames are drawn: a `<canvas>` (or anything with a 2D context of that shape). */\nexport interface CanvasEffectTarget {\n\twidth: number;\n\theight: number;\n\tgetContext(id: '2d'): { putImageData(image: ImageData, x: number, y: number): void } | null;\n}\n\nexport interface CanvasEffectClock {\n\tnow(): number;\n\tframe(callback: () => void): () => void;\n}\n\nconst defaultClock: CanvasEffectClock = {\n\tnow: () => (typeof performance !== 'undefined' ? performance.now() : Date.now()),\n\tframe: (cb) => {\n\t\tif (typeof requestAnimationFrame === 'undefined') {\n\t\t\tconst t = setTimeout(cb, 16);\n\t\t\treturn () => clearTimeout(t);\n\t\t}\n\t\tconst id = requestAnimationFrame(cb);\n\t\treturn () => cancelAnimationFrame(id);\n\t}\n};\n\n/** One pool for the canvas effects of a page. */\nexport const canvasEffectsPool = new EffectRendererPool({ maxRenderers: 6 });\n\nfunction draw(target: CanvasEffectTarget, p: EffectPixels) {\n\tconst ctx = target.getContext('2d');\n\tif (!ctx || typeof ImageData === 'undefined') return;\n\tctx.putImageData(new ImageData(new Uint8ClampedArray(p.data), p.width, p.height), 0, 0);\n}\n\n/**\n * Draws a node's effect into `target` (sized to the node in device pixels): one frame for a still\n * effect or when `playing` is false, else a frame per animation frame (one render in flight at a\n * time) until `stop`. `onError` hears a failed render, after which drawing stops.\n */\nexport function runCanvasNodeEffect(options: { provider: EffectProvider; effect: CanvasShapeEffect; target: CanvasEffectTarget; animate?: boolean; clock?: CanvasEffectClock; pool?: EffectRendererPool; onError?: (error: unknown) => void }): { stop(): void } {\n\tconst { provider, effect, target } = options;\n\tconst clock = options.clock ?? defaultClock;\n\tconst pool = options.pool ?? canvasEffectsPool;\n\tconst size = { width: Math.max(1, Math.round(target.width)), height: Math.max(1, Math.round(target.height)) };\n\tconst start = clock.now();\n\tlet stopped = false;\n\tlet cancel = () => {};\n\tconst tick = () => {\n\t\tif (stopped) return;\n\t\tconst time = options.animate === false || effect.playing === false ? 0 : clock.now() - start;\n\t\tpool.render(provider, effect.effect, size, { time, values: effect.values }).then(\n\t\t\t(p) => {\n\t\t\t\tif (stopped) return;\n\t\t\t\tdraw(target, p);\n\t\t\t\tif (options.animate !== false && effect.playing !== false) cancel = clock.frame(tick);\n\t\t\t},\n\t\t\t(e: unknown) => {\n\t\t\t\tif (!stopped) options.onError?.(e);\n\t\t\t}\n\t\t);\n\t};\n\ttick();\n\treturn {\n\t\tstop() {\n\t\t\tstopped = true;\n\t\t\tcancel();\n\t\t}\n\t};\n}\n\n/** Plays a scene transition from `from` to `to` (same size as `target`), resolving when it ends. */\nexport async function playCanvasSceneTransition(options: { provider: EffectProvider; transition: CanvasSceneTransition; from: EffectPixels; to: EffectPixels; target: CanvasEffectTarget; clock?: CanvasEffectClock; pool?: EffectRendererPool }): Promise<void> {\n\tconst { provider, transition, from, to, target } = options;\n\tconst clock = options.clock ?? defaultClock;\n\tconst pool = options.pool ?? canvasEffectsPool;\n\tconst duration = transition.duration ?? 600;\n\tconst size = { width: from.width, height: from.height };\n\tconst start = clock.now();\n\tfor (;;) {\n\t\tconst time = Math.min(duration, clock.now() - start);\n\t\tconst p = await pool.render(provider, transition.effect, size, { time, progress: time / duration, values: transition.values, inputs: { from, to } });\n\t\tdraw(target, p);\n\t\tif (time >= duration) return;\n\t\tawait new Promise<void>((resolve) => clock.frame(resolve));\n\t}\n}\n\n/**\n * The pixels of a DOM element as it looks now (the canvas view, for a scene transition), at\n * `width × height` device pixels. Browser only; overlays marked `data-capture=\"overlay\"` are left out.\n */\nexport async function captureCanvasView(element: HTMLElement, size: { width: number; height: number }): Promise<EffectPixels> {\n\tconst url = await rasterizeElement(element, { pixelWidth: size.width, pixelHeight: size.height, exclude: CAPTURE_OVERLAY_SELECTORS });\n\tconst doc = element.ownerDocument;\n\tconst image = new (doc.defaultView as Window & typeof globalThis).Image();\n\tawait new Promise<void>((resolve, reject) => {\n\t\timage.onload = () => resolve();\n\t\timage.onerror = () => reject(new Error('The canvas view could not be captured.'));\n\t\timage.src = url;\n\t});\n\tconst canvas = doc.createElement('canvas');\n\tcanvas.width = size.width;\n\tcanvas.height = size.height;\n\tconst ctx = canvas.getContext('2d');\n\tif (!ctx) throw new Error('The canvas view could not be captured (no 2D canvas).');\n\tctx.drawImage(image, 0, 0, size.width, size.height);\n\tconst data = ctx.getImageData(0, 0, size.width, size.height).data;\n\treturn { width: size.width, height: size.height, data };\n}\n\n/**\n * Moves a canvas view to a scene through its transition: captures the view, `jump`s there (no\n * animated move), waits for the view to draw, captures it again and plays the transition on an\n * overlay canvas laid over `element` (removed afterwards). Falls back to just jumping when\n * anything is missing or fails.\n */\nexport async function transitionCanvasView(options: { provider: EffectProvider; transition: CanvasSceneTransition; element: HTMLElement; jump: () => void; scale?: number; clock?: CanvasEffectClock }): Promise<void> {\n\tconst { element } = options;\n\tconst clock = options.clock ?? defaultClock;\n\tconst scale = options.scale ?? Math.min(1, typeof devicePixelRatio === 'number' ? devicePixelRatio : 1);\n\tconst size = { width: Math.max(1, Math.round(element.clientWidth * scale)), height: Math.max(1, Math.round(element.clientHeight * scale)) };\n\tlet from: EffectPixels;\n\ttry {\n\t\tfrom = await captureCanvasView(element, size);\n\t} catch {\n\t\toptions.jump();\n\t\treturn;\n\t}\n\tconst overlay = element.ownerDocument.createElement('canvas');\n\toverlay.width = size.width;\n\toverlay.height = size.height;\n\toverlay.setAttribute('data-capture', 'overlay');\n\toverlay.setAttribute('data-canvas-transition', options.transition.effect);\n\tObject.assign(overlay.style, { position: 'absolute', inset: '0', width: '100%', height: '100%', pointerEvents: 'none', zIndex: '1000' });\n\toverlay.getContext('2d')?.putImageData(new ImageData(new Uint8ClampedArray(from.data), size.width, size.height), 0, 0);\n\tif (getComputedStyle(element).position === 'static') element.style.position = 'relative';\n\telement.appendChild(overlay);\n\ttry {\n\t\toptions.jump();\n\t\tawait new Promise<void>((r) => clock.frame(() => clock.frame(r)));\n\t\tconst to = await captureCanvasView(element, size);\n\t\tawait playCanvasSceneTransition({ provider: options.provider, transition: options.transition, from, to, target: overlay, clock });\n\t} catch {\n\t\t// The jump has happened; the transition is only decoration.\n\t} finally {\n\t\toverlay.remove();\n\t}\n}\n","/**\n * Agent tools for provider effects on the canvas: the shared effect tools on shape nodes\n * (`apply_effect` fills a shape with a generator or lays an overlay over it), plus\n * `set_canvas_effect_mode`, `list_scene_transitions` and `set_scene_transition`.\n */\n\nimport { createEffectToolProvider, type AppliedEffectInfo, type EffectChangeResult } from '../effects/tools.js';\nimport type { EffectValue } from '../effects/types.js';\nimport type { AgentTool, AgentToolProvider } from '../tools/registry.js';\nimport type { CanvasEffectsController } from './effects.js';\nimport type { CanvasShapeEffect } from './shapeGeometry.js';\n\nexport interface CanvasEffectsToolProviderOptions {\n\t/** Provider id (unique per registry). Default `canvas-effects`. */\n\tid?: string;\n\t/** Always prefix tool names with `<namespace>__`. */\n\tnamespace?: string;\n\t/** The canvas cannot be edited: write tools are hidden. A string is the reason. */\n\treadOnly?: boolean | string | (() => boolean | string);\n\t/** Lets the tool list follow changes (e.g. the host's node store subscribe). */\n\tsubscribe?: (notify: () => void) => () => void;\n}\n\nconst info = (e: CanvasShapeEffect): AppliedEffectInfo => ({\n\tid: 'effect',\n\teffect: e.effect,\n\t...(e.name ? { name: e.name } : {}),\n\t...(e.provider ? { provider: e.provider } : {}),\n\trole: e.mode === 'fill' ? 'fill' : 'overlay',\n\tenabled: e.playing !== false,\n\topacity: e.opacity ?? 1,\n\tvalues: { ...e.values }\n});\n\n/** The effect tools for a canvas (see the module comment). */\nexport function createCanvasEffectsToolProvider(controller: CanvasEffectsController, options: CanvasEffectsToolProviderOptions = {}): AgentToolProvider {\n\tconst readOnlyReason = (): string | null => {\n\t\tconst ro = typeof options.readOnly === 'function' ? options.readOnly() : options.readOnly;\n\t\treturn ro ? (typeof ro === 'string' ? ro : 'The canvas is read-only.') : null;\n\t};\n\tconst result = (nodeId: string, effect?: CanvasShapeEffect): EffectChangeResult => {\n\t\tconst now = controller.list(nodeId)[0]?.effect;\n\t\treturn { target: nodeId, effect: info((effect ?? now)!), effects: now ? [info(now)] : [] };\n\t};\n\tconst once = (instance: string) => {\n\t\tif (instance !== 'effect') throw new Error('A shape node has one effect, \"effect\".');\n\t};\n\treturn createEffectToolProvider({\n\t\tid: options.id ?? 'canvas-effects',\n\t\tnamespace: options.namespace,\n\t\ttarget: { noun: 'node', id: 'A shape node id on the canvas (a node with data.shape).' },\n\t\tapplyUses: ['generator', 'overlay', 'title', 'interactive'],\n\t\tapplyDetails:\n\t\t\t'Generators fill the shape (clipped to its outline); overlays, titles and interactive effects draw over the node\\'s box (set_canvas_effect_mode switches). A node has one effect: this replaces it. enabled false holds the first frame; opacity mixes it. index does not apply.',\n\t\tprovider: () => controller.effectsProvider,\n\t\treadOnlyReason,\n\t\tlist: (target) => controller.list(target).map((e) => ({ target: e.nodeId, effects: [info(e.effect)] })),\n\t\tapply: async (target, effect, args) => {\n\t\t\tif (args.index !== undefined) throw new Error('index does not apply: a shape node has one effect.');\n\t\t\tconst provider = controller.effectsProvider;\n\t\t\tconst d = provider ? await provider.describe(effect) : null;\n\t\t\tconst mode = d && !d.uses.includes('generator') ? 'overlay' : 'fill';\n\t\t\tconst applied = await controller.applyNodeEffect(target, effect, { mode, values: args.values as Record<string, EffectValue> | undefined, opacity: args.opacity });\n\t\t\tif (args.enabled === false) await controller.updateNodeEffect(target, { playing: false });\n\t\t\treturn result(target, args.enabled === false ? { ...applied, playing: false } : applied);\n\t\t},\n\t\tupdate: async (target, instance, changes) => {\n\t\t\tonce(instance);\n\t\t\tif (changes.index !== undefined) throw new Error('index does not apply: a shape node has one effect.');\n\t\t\tconst e = await controller.updateNodeEffect(target, { values: changes.values as Record<string, EffectValue | null> | undefined, opacity: changes.opacity, playing: changes.enabled });\n\t\t\treturn result(target, e);\n\t\t},\n\t\tremove: async (target, instance) => {\n\t\t\tonce(instance);\n\t\t\tconst was = controller.removeNodeEffect(target);\n\t\t\treturn { target, effect: info(was), effects: [] };\n\t\t},\n\t\tsubscribe: options.subscribe ? (notify) => options.subscribe!(notify) : (notify) => controller.effects.subscribe(() => notify()),\n\t\textraTools: (ctx): AgentTool[] => [\n\t\t\t{\n\t\t\t\tname: 'set_canvas_effect_mode',\n\t\t\t\ttitle: \"Set a node effect's mode\",\n\t\t\t\tdescription: 'Switches a shape node\\'s effect between fill (paints the inside, clipped to the outline) and overlay (draws over the node\\'s box). One undoable step. Returns the effect.',\n\t\t\t\tinputSchema: {\n\t\t\t\t\ttype: 'object',\n\t\t\t\t\tproperties: {\n\t\t\t\t\t\tnode: { type: 'string', minLength: 1, description: 'A shape node id from list_applied_effects.' },\n\t\t\t\t\t\tmode: { type: 'string', enum: ['fill', 'overlay'], description: 'fill or overlay.' }\n\t\t\t\t\t},\n\t\t\t\t\trequired: ['node', 'mode'],\n\t\t\t\t\tadditionalProperties: false\n\t\t\t\t},\n\t\t\t\tannotations: { title: \"Set a node effect's mode\", ...ctx.annotations.writeIdempotent },\n\t\t\t\thandler: async (args) => {\n\t\t\t\t\tctx.requireProvider();\n\t\t\t\t\treturn { node: args.node, effect: info(await controller.updateNodeEffect(args.node, { mode: args.mode })) };\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'list_scene_transitions',\n\t\t\t\ttitle: 'List scene transitions',\n\t\t\t\tdescription: 'Lists the canvas Scenes in order with the transition effect played when moving to each (or null). Works without a provider.',\n\t\t\t\tinputSchema: { type: 'object', properties: {}, additionalProperties: false },\n\t\t\t\tannotations: { title: 'List scene transitions', ...ctx.annotations.read },\n\t\t\t\thandler: () => ({ scenes: controller.scenes().map((s) => ({ id: s.id, name: s.name, transition: s.transition ?? null })) })\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'set_scene_transition',\n\t\t\t\ttitle: 'Set a scene transition',\n\t\t\t\tdescription:\n\t\t\t\t\t'Sets the provider transition effect (search_effects with use transition) played when the canvas moves to a scene: the view before and after are captured and the effect blends between them. effect null removes it. Returns the transition.',\n\t\t\t\tinputSchema: {\n\t\t\t\t\ttype: 'object',\n\t\t\t\t\tproperties: {\n\t\t\t\t\t\tscene: { type: 'string', minLength: 1, description: 'A scene id from list_scene_transitions.' },\n\t\t\t\t\t\teffect: { anyOf: [ctx.effectIdSchema(['transition']), { type: 'null' }], description: 'A transition effect id from search_effects, or null to remove the transition.' },\n\t\t\t\t\t\tvalues: ctx.valuesSchema(\"Control values over the effect's defaults\"),\n\t\t\t\t\t\tduration: { type: 'number', exclusiveMinimum: 0, description: \"Length in ms (default: the effect's own, else 600).\" }\n\t\t\t\t\t},\n\t\t\t\t\trequired: ['scene', 'effect'],\n\t\t\t\t\tadditionalProperties: false\n\t\t\t\t},\n\t\t\t\tannotations: { title: 'Set a scene transition', ...ctx.annotations.writeIdempotent },\n\t\t\t\thandler: async (args) => {\n\t\t\t\t\tif (args.effect !== null) ctx.requireProvider();\n\t\t\t\t\treturn { scene: args.scene, transition: await controller.setSceneTransition(args.scene, args.effect, { values: args.values, duration: args.duration }) };\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/canvas/index.cjs","../../src/canvas/trajectory.ts","../../src/canvas/displacement.ts","../../src/canvas/clipboard.ts","../../src/canvas/view.ts","../../src/canvas/streaming.ts","../../src/canvas/arrange.ts","../../src/canvas/guides.ts","../../src/canvas/connectors.ts","../../src/canvas/scenes.ts","../../src/canvas/shapeNodes.ts","../../src/canvas/resizeSnap.ts","../../src/canvas/autoLayout.ts","../../src/canvas/arrangeController.ts","../../src/canvas/measure.ts","../../src/canvas/autoLayoutController.ts","../../src/canvas/tokenBindings.ts","../../src/canvas/animation/model.ts","../../src/canvas/animation/evaluate.ts","../../src/canvas/animation/edit.ts","../../src/canvas/animation/export.ts","../../src/canvas/animation/timeline.ts","../../src/canvas/animation/controller.ts","../../src/canvas/components.ts","../../src/canvas/componentsController.ts","../../src/canvas/freehand.ts","../../src/canvas/drawController.ts","../../src/canvas/mermaid/layout.ts","../../src/canvas/mermaid/toFlow.ts","../../src/canvas/mermaid/view.ts","../../src/canvas/mermaid/stream.ts","../../src/canvas/mermaid/theme.ts","../../src/canvas/mermaid/scene-kit.ts","../../src/canvas/mermaid/families/pie.ts","../../src/canvas/mermaid/families/charts.ts","../../src/canvas/mermaid/families/sequence.ts","../../src/canvas/mermaid/families/journey.ts","../../src/canvas/mermaid/families/quadrant.ts","../../src/canvas/mermaid/families/gitgraph.ts","../../src/canvas/mermaid/families/mindmap.ts","../../src/canvas/mermaid/families/c4.ts","../../src/canvas/mermaid/families/architecture.ts","../../src/canvas/mermaid/families/gantt.ts","../../src/canvas/mermaid/families/timeline.ts","../../src/canvas/mermaid/families/index.ts","../../src/canvas/mermaid/scene.ts","../../src/canvas/mermaid/shapes.ts","../../src/canvas/mermaid/export.ts","../../src/canvas/mermaid/index.ts","../../src/canvas/agent-presence.ts","../../src/canvas/capture.ts","../../src/canvas/effects.ts","../../src/canvas/effect-player.ts","../../src/canvas/effect-tools.ts"],"names":["n","next","size","best","inside","scenes","toIndex","defaultId","done","rect","layout","sizeOf","lim","depth","px","EPS","A","text","camel","stringRecord","measured","out","isText","old","root","after","matches","nodes","target","info","set","finite","dagre","num","positions","clampHeight","NODES_PER_RANK","mix","document","estimateTextWidth","TITLE_SIZE","line","M","round","place","r2","LINE_HEIGHT","titleMark","colours","ellipse","boxOf","arc"],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACF,yDAA8B;AAC9B;AACE;AACA;AACF,yDAA8B;AAC9B;AACE;AACF,yDAA8B;AAC9B;AACE;AACF,yDAA8B;AAC9B,iCAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACF,yDAA8B;AAC9B,iCAA8B;AAC9B;AACE;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;A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ACA,MAAA;AACF,MAAA;AACL,IAAA;AACD,EAAA;AACI,EAAA;AACEC,EAAAA;AACD,IAAA;AACJ,IAAA;AACA,IAAA;AACO,IAAA;AACC,IAAA;AACT,EAAA;AACI,EAAA;AACGA,EAAAA;AACR;AAiBS;AAMF,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACF,EAAA;AACF,EAAA;AACA,EAAA;AACC,IAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACA,EAAA;AACK,IAAA;AACL,EAAA;AACA,EAAA;AACK,IAAA;AAAyC,IAAA;AAE9C,EAAA;AACM,EAAA;AACJ,IAAA;AACA,IAAA;AACE,IAAA;AACA,EAAA;AACE,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACG,IAAA;AACA,EAAA;AACE,EAAA;AACF,IAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACQ,IAAA;AACT,EAAA;AACI,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAES;AACD,EAAA;AACR;AAOgB;AAKT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACJ,EAAA;AACG,EAAA;AACG,EAAA;AACR;AAMgB;AAKT,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACH,IAAA;AACD,IAAA;AACJ,EAAA;AACM,EAAA;AACR;AAiFM;AAGU;AACV,EAAA;AACC,EAAA;AACF,EAAA;AACA,EAAA;AACE,EAAA;AACC,EAAA;AACR;AAES;AACW,EAAA;AACT,EAAA;AACN,EAAA;AACI,EAAA;AACT;AAES;AACF,EAAA;AACC,EAAA;AACR;AAGgB;AAIT,EAAA;AACF,EAAA;AACA,EAAA;AACK,EAAA;AACV;AAGgB;AAIT,EAAA;AACF,EAAA;AACA,EAAA;AACE,EAAA;AACF,EAAA;AACK,EAAA;AACV;AAES;AACF,EAAA;AACC,EAAA;AACN,IAAA;AACA,IAAA;AACD,EAAA;AACD;AAES;AAIJ,EAAA;AACG,EAAA;AACF,IAAA;AACJ,IAAA;AACA,IAAA;AACO,IAAA;AACP,IAAA;AACD,EAAA;AACD;AASgB;AAMT,EAAA;AACD,EAAA;AACC,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACK,IAAA;AACF,MAAA;AACH,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACI,IAAA;AACL,IAAA;AACM,IAAA;AACN,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAUgB;AAKT,EAAA;AACF,EAAA;AACI,IAAA;AACR,EAAA;AACM,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACL,MAAA;AACM,MAAA;AACD,MAAA;AACL,MAAA;AACM,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACK,IAAA;AACF,MAAA;AACH,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACI,IAAA;AACL,IAAA;AACM,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACI,QAAA;AACH,QAAA;AACA,QAAA;AACS,MAAA;AACX,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAQgB;AAKT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACF,IAAA;AACC,MAAA;AACJ,MAAA;AACD,IAAA;AACI,IAAA;AACA,IAAA;AACL,EAAA;AACO,EAAA;AACR;AVm6CY;AACA;AWxnEC;AACZ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAGgB;AACL,EAAA;AACA,EAAA;AACD,EAAA;AACV;AAMgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACA,EAAA;AACD,EAAA;AACD,EAAA;AACT;AAkCS;AACA,EAAA;AACC,EAAA;AACA,EAAA;AACF,EAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AACM,EAAA;AACG,EAAA;AACF,EAAA;AACR;AAES;AACA,EAAA;AACF,EAAA;AACG,EAAA;AACF,EAAA;AACR;AAegB;AAMT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACAC,EAAAA;AAEA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAGA,EAAA;AACC,IAAA;AACF,IAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACCP,MAAAA;AACN,MAAA;AACD,IAAA;AACI,IAAA;AACE,IAAA;AACL,MAAA;AACK,QAAA;AACA,QAAA;AACL,MAAA;AACD,IAAA;AACI,IAAA;AACA,IAAA;AACC,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACO,IAAA;AACR,EAAA;AAEU,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACJ,EAAA;AACC,IAAA;AACF,IAAA;AACC,MAAA;AACC,MAAA;AACN,IAAA;AACK,IAAA;AACN,EAAA;AAEI,EAAA;AACA,EAAA;AACE,EAAA;AAEF,EAAA;AACC,IAAA;AAEG,MAAA;AAEA,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACI,QAAA;AACC,QAAA;AACL,QAAA;AACD,MAAA;AACC,QAAA;AACA,QAAA;AACI,QAAA;AACC,QAAA;AACL,QAAA;AACD,MAAA;AACM,MAAA;AACF,MAAA;AACH,QAAA;AACD,MAAA;AACD,IAAA;AACC,MAAA;AACM,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACP,IAAA;AACC,MAAA;AACM,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACP,IAAA;AACM,EAAA;AACF,IAAA;AACA,IAAA;AACL,EAAA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AA;AACT,EAAA;AAGM,EAAA;AACF,EAAA;AACE,EAAA;AACG,EAAA;AACA,EAAA;AACL,EAAA;AACG,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AACI,EAAA;AACG,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AACU,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AAGF,EAAA;AACE,EAAA;AACI,EAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACH,MAAA;AACM,IAAA;AACF,MAAA;AACE,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACC,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACM,EAAA;AACE,IAAA;AACD,IAAA;AACA,IAAA;AACD,IAAA;AACE,IAAA;AACP,IAAA;AACO,MAAA;AACA,MAAA;AACF,MAAA;AACA,MAAA;AAAoC,MAAA;AAC0B,MAAA;AAG9D,MAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AACA,IAAA;AACA,EAAA;AACF;AAUS;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACG,IAAA;AACF,IAAA;AACL,EAAA;AACI,EAAA;AACK,EAAA;AACV;AAES;AACE,EAAA;AACJX,EAAAA;AACG,EAAA;AACV;AAES;AACE,EAAA;AACJ,EAAA;AACG,EAAA;AACV;AAoBS;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAEM;AAIG;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACL,IAAA;AACO,MAAA;AACD,MAAA;AACL,MAAA;AACD,IAAA;AACO,IAAA;AACR,EAAA;AACM,EAAA;AACE,IAAA;AACP,IAAA;AACD,EAAA;AACM,EAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACD,MAAA;AACA,MAAA;AACL,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACM,EAAA;AACD,EAAA;AACI,IAAA;AACJ,IAAA;AACE,MAAA;AACL,MAAA;AACD,IAAA;AACA,EAAA;AACG,EAAA;AACC,EAAA;AACA,IAAA;AACI,IAAA;AACF,IAAA;AACE,IAAA;AACA,IAAA;AACJ,IAAA;AACC,MAAA;AACJ,MAAA;AAAuE,QAAA;AAAI,QAAA;AAAO,MAAA;AAC9E,MAAA;AACL,IAAA;AACK,MAAA;AACJ,MAAA;AAAyE,QAAA;AAAI,QAAA;AAAO,MAAA;AAC9E,IAAA;AACD,MAAA;AACD,MAAA;AACJ,MAAA;AACK,QAAA;AACH,UAAA;AACA,UAAA;AACD,QAAA;AACA,QAAA;AACI,QAAA;AACH,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACK,MAAA;AACL,MAAA;AACD,IAAA;AACQ,IAAA;AACA,IAAA;AACR,EAAA;AACK,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAOS;AACF,EAAA;AACC,IAAA;AACC,IAAA;AACP,EAAA;AACG,EAAA;AACE,EAAA;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACQ,IAAA;AACD,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAGS;AASF,EAAA;AACA,EAAA;AACC,IAAA;AACC,IAAA;AACP,EAAA;AACK,EAAA;AACA,EAAA;AACE,IAAA;AACH,IAAA;AACJ,EAAA;AACK,EAAA;AACE,IAAA;AACH,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACD,IAAA;AACA,EAAA;AACM,EAAA;AACR;AAES;AACA,EAAA;AACD,EAAA;AACA,IAAA;AACDE,IAAAA;AACE,IAAA;AACP,EAAA;AACF;AAES;AACE,EAAA;AACJ,EAAA;AACI,EAAA;AACH,EAAA;AACD,EAAA;AACE,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACAU,IAAAA;AACE,IAAA;AACA,IAAA;AACT,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACN,IAAA;AACM,IAAA;AACN,IAAA;AACO,IAAA;AACC,IAAA;AACT,EAAA;AACD;AAES;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACP,EAAA;AACC,IAAA;AACA,IAAA;AACO,IAAA;AACA,IAAA;AACR,EAAA;AACQ,EAAA;AACA,EAAA;AAEJ,EAAA;AACJ,EAAA;AACO,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACI,EAAA;AACI,EAAA;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACH,MAAA;AACC,QAAA;AACE,QAAA;AACF,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACM,IAAA;AACA,IAAA;AACA,IAAA;AACP,EAAA;AACD;AASS;AACE,EAAA;AACF,EAAA;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACH,EAAA;AACA,EAAA;AACA,EAAA;AACE,EAAA;AACP,IAAA;AACO,MAAA;AACA,MAAA;AACF,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACE,MAAA;AACA,MAAA;AACN,MAAA;AACC,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACF,MAAA;AACA,MAAA;AACC,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACF,MAAA;AACM,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,MAAA;AACH,QAAA;AACI,QAAA;AACA,QAAA;AACL,MAAA;AACD,IAAA;AACA,EAAA;AACF;AASgB;AACL,EAAA;AACA,EAAA;AACD,EAAA;AACV;AAES;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACD,IAAA;AACC,IAAA;AACL,IAAA;AACI,IAAA;AACL,EAAA;AACM,EAAA;AACN,EAAA;AACA,EAAA;AACO,EAAA;AACR;AAGgB;AACL,EAAA;AACV,EAAA;AACS,EAAA;AACV;AAMgB;AACR,EAAA;AACR;AAGgB;AACL,EAAA;AACJ,EAAA;AAEAC,EAAAA;AACN,EAAA;AACM,EAAA;AACN,EAAA;AACS,EAAA;AACV;AAOgB;AAML,EAAA;AACA,EAAA;AACD,EAAA;AACV;AAGgB;AAKR,EAAA;AACR;AAGgB;AACR,EAAA;AAGR;AAGgB;AACT,EAAA;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACC,EAAA;AACR;AAQgB;AAKT,EAAA;AACA,EAAA;AACD,EAAA;AACD,EAAA;AACE,EAAA;AACA,IAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACF,MAAA;AACA,MAAA;AACJ,MAAA;AACD,IAAA;AACM,IAAA;AACA,IAAA;AACF,IAAA;AACJ,IAAA;AACM,IAAA;AACC,IAAA;AACH,MAAA;AACG,MAAA;AACP,IAAA;AACA,EAAA;AACM,EAAA;AACR;AAkBgB;AAMT,EAAA;AACA,EAAA;AACA,EAAA;AACD,EAAA;AACK,EAAA;AACJ,EAAA;AACAC,EAAAA;AACA,EAAA;AACA,EAAA;AAIA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACJ,EAAA;AACF,EAAA;AACJ,EAAA;AACO,IAAA;AAIF,IAAA;AACL,EAAA;AACO,EAAA;AACD,EAAA;AACA,EAAA;AACI,EAAA;AACN,EAAA;AACA,EAAA;AACG,IAAA;AAOA,IAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACF,EAAA;AACA,IAAA;AAOA,IAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACT,EAAA;AACS,EAAA;AACV;AAOgB;AACT,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AAEA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AAEF,EAAA;AACF,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACAN,EAAAA;AACA,EAAA;AACA,EAAA;AACD,IAAA;AACC,IAAA;AACC,IAAA;AACF,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACA,EAAA;AACO,IAAA;AACA,MAAA;AACL,MAAA;AACM,IAAA;AACR,EAAA;AACA,EAAA;AACO,EAAA;AACR;AZm+DY;AACA;AahmGI;AAGX,EAAA;AACA,EAAA;AACA,EAAA;AAEA,EAAA;AAGA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEE,EAAA;AAGA,EAAA;AACD,IAAA;AACJ,IAAA;AACQ,oBAAA;AACT,EAAA;AAGS,EAAA;AACJ,IAAA;AACA,IAAA;AACG,MAAA;AACA,MAAA;AACAE,MAAAA;AACFA,MAAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACM,IAAA;AACA,IAAA;AACF,IAAA;AACH,MAAA;AACM,IAAA;AACF,MAAA;AACL,IAAA;AACO,IAAA;AACR,EAAA;AAES,EAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACE,IAAA;AACA,IAAA;AACF,IAAA;AACH,MAAA;AACD,IAAA;AACK,IAAA;AACE,IAAA;AACR,EAAA;AAEMD,EAAAA;AAEA,EAAA;AACD,IAAA;AACH,MAAA;AACD,IAAA;AACA,IAAA;AACM,IAAA;AACL,MAAA;AACD,IAAA;AACA,IAAA;AAMC,MAAA;AACC,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACC,MAAA;AACC,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACQ,IAAA;AACP,MAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACO,IAAA;AACC,IAAA;AACR,IAAA;AACO,MAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AACA,IAAA;AAA8E;AAAA;AAAA;AAAA;AAAA;AAAA;AAQ9E,IAAA;AAKO,MAAA;AACA,MAAA;AACD,MAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACF,MAAA;AACC,MAAA;AACL,MAAA;AACD,IAAA;AACQ,IAAA;AACR,IAAA;AACA,IAAA;AACA,IAAA;AACQ,IAAA;AAAmG;AAE3G,IAAA;AAAyG;AAAA;AAAA;AAAA;AAAA;AAAA;AAQzG,IAAA;AAKO,MAAA;AACA,MAAA;AACD,MAAA;AACC,MAAA;AACA,MAAA;AACF,MAAA;AACC,MAAA;AACL,MAAA;AACD,IAAA;AAAA;AAEA,IAAA;AAAuG;AAAA;AAAA;AAAA;AAAA;AAOvG,IAAA;AACO,MAAA;AACA,MAAA;AACD,MAAA;AACC,MAAA;AACA,MAAA;AACF,MAAA;AACC,MAAA;AACL,MAAA;AACD,IAAA;AAAA;AAEA,IAAA;AAA+G;AAAA;AAAA;AAAA;AAM/G,IAAA;AACC,MAAA;AACC,QAAA;AAAkB,UAAA;AAA0B,UAAA;AAC5C,QAAA;AAAoB,UAAA;AAA4B,UAAA;AAChD,QAAA;AAAmB,UAAA;AAA2B,UAAA;AAC9C,QAAA;AAAiB,UAAA;AAAyB,UAAA;AAC1C,QAAA;AAAoB,UAAA;AAA4B,UAAA;AAChD,QAAA;AAAoB,UAAA;AAA4B,UAAA;AAChD,QAAA;AAA6B,UAAA;AAAqC,UAAA;AAClE,QAAA;AAA2B,UAAA;AAAmC,UAAA;AAC9D,QAAA;AAAa,UAAA;AAAmB,UAAA;AAChC,QAAA;AAAe,UAAA;AAAqB,UAAA;AACpC,QAAA;AAAqB,UAAA;AAA2B,UAAA;AAChD,QAAA;AAAqB,UAAA;AAA2B,UAAA;AAChD,QAAA;AAAqB,UAAA;AAA2B,UAAA;AAChD,QAAA;AAAmB,UAAA;AAAyB,UAAA;AAC5C,QAAA;AAAS,UAAA;AACV,MAAA;AACD,IAAA;AAAA;AAGA,IAAA;AACO,MAAA;AACN,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOK,IAAA;AACA,MAAA;AACE,MAAA;AACA,MAAA;AACD,MAAA;AACC,MAAA;AACFA,QAAAA;AACA,QAAA;AACH,QAAA;AACA,QAAA;AACA,MAAA;AACD,MAAA;AACI,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,IAAA;AACO,MAAA;AACJ,QAAA;AAEF,MAAA;AACI,MAAA;AACC,QAAA;AAGJ,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AAEI,QAAA;AACJ,QAAA;AACD,MAAA;AACI,MAAA;AACE,MAAA;AACA,MAAA;AACD,MAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACFA,QAAAA;AACA,QAAA;AACH,QAAA;AACA,QAAA;AACA,MAAA;AACD,MAAA;AACM,MAAA;AACN,MAAA;AACI,MAAA;AAEE,MAAA;AACA,MAAA;AACN,MAAA;AACK,QAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACF,IAAA;AAAA;AAAA;AAAA;AAAA;AAMA,IAAA;AACO,MAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASO,IAAA;AACF,MAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACD,MAAA;AACC,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACI,QAAA;AACH,UAAA;AACC,YAAA;AAAwB,cAAA;AAExB,YAAA;AACD,UAAA;AACD,QAAA;AACA,QAAA;AACD,MAAA;AACK,MAAA;AACC,MAAA;AACD,MAAA;AACJ,QAAA;AACA,QAAA;AACD,MAAA;AACM,MAAA;AACFA,QAAAA;AACA,QAAA;AACA,QAAA;AACH,QAAA;AACA,QAAA;AACA,MAAA;AACD,MAAA;AACI,MAAA;AACJ,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAAA;AAAA;AAAA;AAMA,IAAA;AACO,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACM,MAAA;AACD,MAAA;AACC,MAAA;AACD,MAAA;AACC,MAAA;AAKF,MAAA;AACL,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,IAAA;AACO,MAAA;AACJ,QAAA;AACF,MAAA;AACK,MAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,MAAA;AACC,QAAA;AACJ,QAAA;AACD,MAAA;AACI,MAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACI,QAAA;AACJ,QAAA;AAAoB,UAAA;AAEpB,QAAA;AACD,MAAA;AACK,MAAA;AACD,MAAA;AACC,MAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,QAAA;AACA,QAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACM,MAAA;AACD,MAAA;AACJ,QAAA;AACA,QAAA;AACD,MAAA;AACM,MAAA;AACFA,QAAAA;AACA,QAAA;AACA,QAAA;AACH,QAAA;AACA,QAAA;AACA,MAAA;AACD,MAAA;AACM,MAAA;AACN,MAAA;AACK,MAAA;AACC,MAAA;AACD,QAAA;AACA,QAAA;AACJ,QAAA;AACA,MAAA;AACI,MAAA;AACJ,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,IAAA;AACO,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACI,MAAA;AACE,MAAA;AACA,MAAA;AACL,QAAA;AACA,QAAA;AACA,MAAA;AACG,MAAA;AACH,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AbglGY;AACA;Ac1nHA;AAcI;AACL,EAAA;AACH,EAAA;AACR;AASc;AACR;AAEG;AACC,EAAA;AACV;AAGS;AACE,EAAA;AACFM,EAAAA;AACT;AAGS;AACC,EAAA;AACV;AAES;AACD,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACV;AAYgB;AACT,EAAA;AACAC,EAAAA;AACI,EAAA;AACL,EAAA;AACD,EAAA;AACI,IAAA;AACA,IAAA;AACR,EAAA;AACI,EAAA;AACI,IAAA;AACA,IAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACO,IAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACF,IAAA;AACD,IAAA;AACN,IAAA;AACI,IAAA;AACL,EAAA;AACM,EAAA;AACI,EAAA;AACF,IAAA;AACD,IAAA;AACN,IAAA;AACI,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAMgB;AACT,EAAA;AACAA,EAAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACD,EAAA;AAEI,EAAA;AACD,EAAA;AACJ,EAAA;AACI,IAAA;AACA,IAAA;AACT,EAAA;AACO,EAAA;AACR;AAmBgB;AAMT,EAAA;AACA,EAAA;AACA,EAAA;AAGF,EAAA;AACE,EAAA;AACD,EAAA;AACC,EAAA;AAEF,EAAA;AACA,EAAA;AACG,IAAA;AACA,IAAA;AACN,IAAA;AACD,EAAA;AACM,EAAA;AACD,EAAA;AACCP,EAAAA;AACG,EAAA;AACV;AAMgB;AAGV,EAAA;AACK,EAAA;AACH,EAAA;AACR;Ad2iHY;AACA;Ae3tHI;AACT,EAAA;AACN,EAAA;AACQ,EAAA;AACT;AAOgB;AACR,EAAA;AACR;AAQgB;AAGT,EAAA;AACA,EAAA;AAGA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AAEE,EAAA;AACA,EAAA;AACF,EAAA;AAEE,EAAA;AACD,IAAA;AACJ,IAAA;AACQ,oBAAA;AACT,EAAA;AAMS,EAAA;AAQF,IAAA;AACA,IAAA;AACA,IAAA;AACAC,IAAAA;AACA,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AAEH,MAAA;AACD,IAAA;AACM,IAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACE,IAAA;AACR,EAAA;AAEM,EAAA;AAEA,EAAA;AAAa;AAEd,IAAA;AACH,MAAA;AACD,IAAA;AAAA;AAAA;AAAA;AAAA;AAMA,IAAA;AACK,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAGA,IAAA;AACC,MAAA;AACD,IAAA;AAAA;AAAA;AAAA;AAAA;AAMA,IAAA;AACO,MAAA;AACF,MAAA;AACJ,MAAA;AACD,IAAA;AAAA;AAAA;AAAA;AAAA;AAMQ,IAAA;AACD,MAAA;AACD,QAAA;AACA,QAAA;AACA,QAAA;AACJ,QAAA;AACD,MAAA;AACM,MAAA;AACN,MAAA;AACD,IAAA;AAAA;AAGA,IAAA;AACO,MAAA;AACA,MAAA;AAEF,MAAA;AACJ,MAAA;AACC,QAAA;AACA,QAAA;AACC,UAAA;AACC,YAAA;AACA,YAAA;AACD,UAAA;AACD,QAAA;AACD,MAAA;AACM,MAAA;AACF,MAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACL,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAAA;AAAA;AAAA;AAMA,IAAA;AACO,MAAA;AACA,MAAA;AACD,MAAA;AACC,MAAA;AACA,MAAA;AACFR,MAAAA;AACAA,MAAAA;AACE,MAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AAAA;AAGA,IAAA;AACO,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,MAAA;AACJ,MAAA;AACD,IAAA;AAAA;AAGA,IAAA;AACO,MAAA;AACA,MAAA;AACD,MAAA;AACC,MAAA;AACAO,MAAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AAAA;AAGA,IAAA;AACO,MAAA;AACF,MAAA;AACL,IAAA;AAAA;AAAA;AAAA;AAAA;AAMA,IAAA;AACK,MAAA;AACC,MAAA;AACC,MAAA;AACN,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAAA;AAAA;AAAA;AAMA,IAAA;AACC,MAAA;AACM,MAAA;AACN,MAAA;AACI,MAAA;AACE,MAAA;AACD,MAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,IAAA;AACO,MAAA;AACA,MAAA;AACN,MAAA;AACC,QAAA;AACI,QAAA;AACH,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AAAiC,UAAA;AAElC,QAAA;AACC,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACC,YAAA;AACA,YAAA;AACD,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACC,YAAA;AACC,cAAA;AACA,cAAA;AACD,YAAA;AACD,UAAA;AACC,YAAA;AACD,UAAA;AACD,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAAA;AAAA;AAAA;AAMA,IAAA;AACO,MAAA;AACA,MAAA;AACN,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAGI,IAAA;AACH,MAAA;AACD,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAO4B,IAAA;AACvB,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACK,MAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACI,MAAA;AACA,MAAA;AACJ,MAAA;AACK,QAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACMC,MAAAA;AAGN,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACI,MAAA;AACJ,MAAA;AACD,IAAA;AAAA;AAGA,IAAA;AACD,EAAA;AAEO,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACE,EAAA;AACD,EAAA;AACR;AfqqHY;AACA;AgB/+HC;AAEA;AAmBC;AAGL;AACF,EAAA;AACFG,EAAAA;AACA,EAAA;AACA,EAAA;AACK,EAAA;AACF,IAAA;AACF,IAAA;AACC,MAAA;AAAa,MAAA;AAElB,IAAA;AAA6C,IAAA;AACxB,IAAA;AAC6B,IAAA;AAE3C,MAAA;AACN,MAAA;AACD,IAAA;AACD,EAAA;AACM,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACF,IAAA;AACE,IAAA;AACF,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACV,EAAA;AACD,EAAA;AACE,EAAA;AACF,EAAA;AACJ,EAAA;AACO,EAAA;AACR;AAMgB;AAMX,EAAA;AACG,IAAA;AACAI,IAAAA;AAEA,IAAA;AACF,IAAA;AACJ,IAAA;AACK,MAAA;AAAqD,MAAA;AAExD,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACK,IAAA;AACE,IAAA;AACR,EAAA;AACM,EAAA;AACF,EAAA;AACM,EAAA;AACH,EAAA;AACR;AAEY;AACP,EAAA;AACA,EAAA;AACGjB,IAAAA;AACC,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACA,EAAA;AACF,EAAA;AACK,EAAA;AACV;AAES;AACF,EAAA;AACC,EAAA;AACR;AAES;AACF,EAAA;AACI,EAAA;AACD,EAAA;AACV;AAEM;AAEG;AACD,EAAA;AACD,IAAA;AACE,MAAA;AACF,MAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACM,IAAA;AACC,MAAA;AACF,MAAA;AACA,MAAA;AACA,MAAA;AACH,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAES;AACD,EAAA;AAGR;AAES;AACD,EAAA;AACN,IAAA;AACA,IAAA;AACM,IAAA;AACC,IAAA;AACP,IAAA;AACO,IAAA;AACAU,MAAAA;AACN,MAAA;AACD,IAAA;AACQ,IAAA;AACDA,MAAAA;AACDA,MAAAA;AACL,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAGsB;AAEb;AACD,EAAA;AACN,IAAA;AACA,IAAA;AACCA,IAAAA;AACM,MAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AACCA,IAAAA;AACM,MAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAGa;AACZ,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACE,IAAA;AACR,IAAA;AACG,IAAA;AACJ,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACP,IAAA;AACG,IAAA;AACJ,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACP,IAAA;AACG,IAAA;AACJ,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACP,IAAA;AAAiB;AAEV,IAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AACQ,IAAA;AACT,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACP,IAAA;AACO,MAAA;AACN,MAAA;AACD,IAAA;AACO,IAAA;AACA,MAAA;AACF,MAAA;AACJ,MAAA;AACD,IAAA;AACQ,IAAA;AACD,MAAA;AACN,MAAA;AACD,IAAA;AACD,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACP,IAAA;AACO,IAAA;AACC,IAAA;AACT,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACP,IAAA;AACO,IAAA;AACC,IAAA;AACT,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACN,IAAA;AACO,IAAA;AACC,IAAA;AACT,EAAA;AACD;AASgD;AAEvC;AACJ,EAAA;AACE,EAAA;AACN,EAAA;AACO,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAEgB;AACP,EAAA;AACT;AAGgB;AACP,EAAA;AACT;AAGgB;AACR,EAAA;AACR;AAGgB;AACT,EAAA;AACC,EAAA;AACR;AAES;AACD,EAAA;AACR;AAES;AACR,EAAA;AACK,IAAA;AACG,MAAA;AAED,MAAA;AACN,IAAA;AACK,IAAA;AACN,EAAA;AACD;AAMgB;AAMT,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACH,IAAA;AACC,IAAA;AACP,EAAA;AACM,EAAA;AACF,EAAA;AACK,EAAA;AACF,EAAA;AACR;AAES;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACA,EAAA;AACC,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACG,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACM,IAAA;AACF,IAAA;AACA,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACK,IAAA;AACE,IAAA;AACR,EAAA;AACS,EAAA;AACV;AAES;AACF,EAAA;AACF,EAAA;AACJ,EAAA;AACK,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACD,IAAA;AACE,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAOgB;AACP,EAAA;AACJ,EAAA;AACE,EAAA;AACD,IAAA;AACE,IAAA;AACF,IAAA;AACG,IAAA;AACP,EAAA;AACM,EAAA;AACR;AAUgB;AAMT,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACD,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACF,EAAA;AACM,EAAA;AACD,EAAA;AACV;AAQgB;AAQT,EAAA;AACD,EAAA;AACD,EAAA;AACG,IAAA;AACD,IAAA;AACD,IAAA;AACG,MAAA;AACP,IAAA;AACD,EAAA;AACM,EAAA;AACF,EAAA;AACE,EAAA;AACA,IAAA;AACC,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACM,IAAA;AACF,MAAA;AACJ,MAAA;AACD,IAAA;AACA,IAAA;AACO,IAAA;AACP,EAAA;AACM,EAAA;AACR;AAMgB;AAQX,EAAA;AACG,IAAA;AACD,IAAA;AACA,IAAA;AACN,EAAA;AACM,EAAA;AACF,EAAA;AACE,EAAA;AACA,IAAA;AACC,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACM,IAAA;AACF,MAAA;AACE,MAAA;AACP,IAAA;AACA,IAAA;AACO,IAAA;AACP,EAAA;AACM,EAAA;AACR;AAQgB;AAMT,EAAA;AACA,EAAA;AACE,EAAA;AACJ,EAAA;AACE,EAAA;AACC,IAAA;AACA,IAAA;AACF,IAAA;AACA,IAAA;AACE,IAAA;AACN,IAAA;AACO,IAAA;AACP,IAAA;AACO,IAAA;AACP,EAAA;AACM,EAAA;AACR;AAMgB;AACX,EAAA;AACE,EAAA;AACC,IAAA;AACA,IAAA;AACN,IAAA;AACI,IAAA;AACJ,IAAA;AACO,IAAA;AACP,EAAA;AACM,EAAA;AACR;AhB21HY;AACA;AiBv7IC;AAgCA;AACR,EAAA;AACA,EAAA;AACGV,IAAAA;AACC,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAEc;AAEL;AACD,EAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACG,IAAA;AACI,IAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AACQ,IAAA;AACD,MAAA;AACN,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAES;AACD,EAAA;AACN,IAAA;AACA,IAAA;AACM,IAAA;AACD,IAAA;AACC,IAAA;AACC,IAAA;AACC,IAAA;AACD,MAAA;AACN,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAES;AACF,EAAA;AACD,EAAA;AACD,EAAA;AACG,IAAA;AACC,IAAA;AACA,EAAA;AACA,IAAA;AACR,EAAA;AACD;AASa;AACZ,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACA,IAAA;AACC,IAAA;AACC,IAAA;AACT,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACA,IAAA;AACC,IAAA;AACC,IAAA;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACA,IAAA;AACC,IAAA;AACC,IAAA;AACJ,MAAA;AACH,MAAA;AACD,IAAA;AACD,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACD,IAAA;AACE,IAAA;AACC,IAAA;AACJ,MAAA;AACH,MAAA;AACD,IAAA;AACD,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACD,IAAA;AACA,IAAA;AACE,IAAA;AACC,IAAA;AACJ,MAAA;AACH,MAAA;AACD,IAAA;AACD,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACM,IAAA;AACE,IAAA;AACD,IAAA;AACD,IAAA;AACA,IAAA;AACC,IAAA;AACE,IAAA;AACD,MAAA;AACD,MAAA;AACC,MAAA;AACF,MAAA;AACA,QAAA;AACH,QAAA;AACD,MAAA;AACI,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAOgB;AACP,EAAA;AACT;AAGgB;AACV,EAAA;AACG,EAAA;AACJ,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAGgB;AACT,EAAA;AACF,EAAA;AACG,EAAA;AACR;AAGgB;AACT,EAAA;AACC,EAAA;AACR;AAGgB;AACT,EAAA;AACD,EAAA;AACE,EAAA;AACR;AAGgB;AACT,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAMgB;AACT,EAAA;AACF,EAAA;AACH,IAAA;AACK,MAAA;AACL,IAAA;AACD,EAAA;AACM,EAAA;AACF,EAAA;AACM,EAAA;AACD,EAAA;AACV;AjBy3IY;AACA;AkBvmJN;AAGU;AACL,EAAA;AACV,EAAA;AACO,IAAA;AACD,IAAA;AACL,IAAA;AACM,MAAA;AACC,MAAA;AACF,MAAA;AACL,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAWgB;AACT,EAAA;AACA,EAAA;AACN,EAAA;AACK,IAAA;AACE,IAAA;AACD,IAAA;AACD,IAAA;AACJ,IAAA;AACM,MAAA;AACC,MAAA;AACA,MAAA;AACF,MAAA;AACH,MAAA;AACF,IAAA;AACI,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAMgB;AACT,EAAA;AACF,EAAA;AACE,EAAA;AACC,IAAA;AACD,IAAA;AACD,IAAA;AACJ,IAAA;AACO,MAAA;AACN,MAAA;AACD,IAAA;AACI,IAAA;AACG,IAAA;AACP,EAAA;AACM,EAAA;AACR;AAMgB;AACR,EAAA;AACR;AlBmlJY;AACA;AmBvoJC;AACH,EAAA;AACT,EAAA;AACO,IAAA;AACD,IAAA;AACA,IAAA;AACN,EAAA;AACD;AAGa;AAEP;AACA;AAGU;AACT,EAAA;AACA,EAAA;AAEC,EAAA;AACR;AAES;AACF,EAAA;AACF,EAAA;AACG,IAAA;AACF,IAAA;AACG,IAAA;AACR,EAAA;AACMA,EAAAA;AACF,EAAA;AACG,EAAA;AACR;AAES;AACJ,EAAA;AACG,IAAA;AACF,IAAA;AACL,EAAA;AACU,EAAA;AACX;AAES;AACJ,EAAA;AACG,EAAA;AACR;AAMA;AAKC,EAAA;AAA6B,IAAA;AAAkC,IAAA;AACzD,IAAA;AACN,EAAA;AAF6B,EAAA;AAAkC,EAAA;AAJ9C,EAAA;AACA,mBAAA;AACA,mBAAA;AAMO,EAAA;AACjB,IAAA;AACD,IAAA;AACE,IAAA;AACR,EAAA;AAEyB,EAAA;AACjB,IAAA;AACR,EAAA;AAEU,EAAA;AACL,IAAA;AACC,IAAA;AACE,MAAA;AACA,MAAA;AACN,MAAA;AACK,QAAA;AACL,MAAA;AACA,MAAA;AACK,MAAA;AACN,IAAA;AACO,IAAA;AACR,EAAA;AAEiB,EAAA;AACT,IAAA;AACR,EAAA;AAEkB,EAAA;AACV,IAAA;AACR,EAAA;AAEQ,EAAA;AACD,IAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AAEA,EAAA;AACO,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AAEU,EAAA;AACJ,IAAA;AACN,EAAA;AAE+B,EAAA;AACxB,IAAA;AACC,IAAA;AACR,EAAA;AAES,EAAA;AACD,IAAA;AACR,EAAA;AAEkB,EAAA;AACb,IAAA;AACG,IAAA;AACA,MAAA;AACA,MAAA;AACD,MAAA;AACC,MAAA;AACN,MAAA;AACA,IAAA;AACF,EAAA;AACD;AAES;AACH,EAAA;AACI,EAAA;AACH,EAAA;AACN,EAAA;AACM,IAAA;AACE,MAAA;AACP,IAAA;AACM,IAAA;AACF,IAAA;AACE,IAAA;AACF,IAAA;AACE,IAAA;AACD,IAAA;AACN,EAAA;AACD;AAGgB;AACT,EAAA;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAGgB;AACT,EAAA;AACA,EAAA;AACF,EAAA;AACG,EAAA;AACR;AAOS;AACF,EAAA;AACA,EAAA;AACI,EAAA;AACF,IAAA;AACD,EAAA;AAEA,IAAA;AACA,IAAA;AACD,IAAA;AACN,EAAA;AACM,EAAA;AACP;AAGgB;AAQT,EAAA;AACA,EAAA;AACI,EAAA;AACF,EAAA;AACD,EAAA;AACA,EAAA;AACR;AAOgB;AAMT,EAAA;AACI,EAAA;AACJ,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACN,IAAA;AACO,MAAA;AACD,MAAA;AACC,MAAA;AACF,MAAA;AACJ,MAAA;AACK,MAAA;AACN,IAAA;AACD,EAAA;AACS,EAAA;AACV;AASgB;AACT,EAAA;AACN,EAAA;AACM,EAAA;AACI,EAAA;AACJ,EAAA;AACI,EAAA;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACL,EAAA;AAEC,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAGgB;AACT,EAAA;AACN,EAAA;AACM,EAAA;AACN,EAAA;AACO,IAAA;AACA,MAAA;AACC,MAAA;AACN,MAAA;AACA,IAAA;AACK,IAAA;AACP,EAAA;AACO,EAAA;AACR;AAMgB;AACT,EAAA;AACN,EAAA;AACM,EAAA;AACN,EAAA;AACA,EAAA;AACS,IAAA;AACF,IAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACA,MAAA;AACF,MAAA;AACL,IAAA;AACM,IAAA;AACP,EAAA;AACO,EAAA;AACR;AAGgB;AACT,EAAA;AACD,EAAA;AACG,EAAA;AACF,EAAA;AACC,EAAA;AACR;AAMgB;AACT,EAAA;AACA,EAAA;AACD,EAAA;AACK,EAAA;AACN,EAAA;AACG,IAAA;AACF,IAAA;AACL,EAAA;AACM,EAAA;AACF,EAAA;AACG,EAAA;AACR;AAGgB;AACT,EAAA;AACA,EAAA;AACF,EAAA;AACG,IAAA;AACP,EAAA;AACM,EAAA;AACC,EAAA;AACR;AAwBgB;AAMX,EAAA;AACE,EAAA;AACN,EAAA;AACM,EAAA;AACA,EAAA;AACI,EAAA;AACN,EAAA;AACJ,EAAA;AACO,IAAA;AACA,IAAA;AACC,IAAA;AACR,EAAA;AACU,EAAA;AACJ,EAAA;AACA,EAAA;AACG,EAAA;AACF,EAAA;AACE,EAAA;AACV;AAGS;AACF,EAAA;AACN,EAAA;AACA,EAAA;AACS,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACP,EAAA;AACD;AAWgB;AAMX,EAAA;AACE,EAAA;AACN,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACJ,EAAA;AACS,IAAA;AACA,IAAA;AACT,EAAA;AACU,EAAA;AACJ,EAAA;AACG,EAAA;AACF,EAAA;AACE,EAAA;AACV;AAGgB;AACT,EAAA;AACA,EAAA;AACN,EAAA;AACM,EAAA;AACN,EAAA;AACK,IAAA;AACE,IAAA;AACD,IAAA;AACL,IAAA;AACO,MAAA;AACN,MAAA;AACD,IAAA;AACD,EAAA;AACM,EAAA;AACC,EAAA;AACR;AAcgB;AACT,EAAA;AACF,EAAA;AACJ,EAAA;AACO,IAAA;AACF,IAAA;AACA,IAAA;AACG,IAAA;AACA,MAAA;AACF,MAAA;AACC,MAAA;AACN,IAAA;AACA,IAAA;AACO,MAAA;AACF,MAAA;AACE,MAAA;AACF,MAAA;AACL,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAagB;AACT,EAAA;AACN,EAAA;AACM,EAAA;AACA,EAAA;AACC,EAAA;AACA,IAAA;AACN,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACI,MAAA;AACH,IAAA;AACH,EAAA;AACD;AAGgB;AACX,EAAA;AACA,EAAA;AACC,IAAA;AACA,IAAA;AACH,MAAA;AACO,IAAA;AACP,MAAA;AACD,IAAA;AACD,EAAA;AACI,EAAA;AACE,EAAA;AACG,EAAA;AACH,EAAA;AAEJ,IAAA;AAOF,EAAA;AACO,EAAA;AACR;AAQgB;AAMT,EAAA;AACI,EAAA;AACJ,EAAA;AACA,EAAA;AACA,IAAA;AACD,IAAA;AACE,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACN,EAAA;AACO,IAAA;AACE,IAAA;AACR,IAAA;AACM,MAAA;AACD,MAAA;AACA,MAAA;AACH,QAAA;AACD,MAAA;AACC,QAAA;AACD,MAAA;AACM,MAAA;AACN,MAAA;AACK,MAAA;AACN,IAAA;AACD,EAAA;AACS,EAAA;AACV;AAKM;AAwBU;AAOT,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACJ,EAAA;AACA,EAAA;AACC,IAAA;AACA,IAAA;AACD,IAAA;AACD,IAAA;AACE,IAAA;AACF,IAAA;AACA,IAAA;AACE,IAAA;AACN,IAAA;AACO,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACL,QAAA;AACI,QAAA;AACL,MAAA;AACI,MAAA;AACC,QAAA;AACH,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACA,QAAA;AACD,MAAA;AACC,QAAA;AACI,QAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACI,IAAA;AACG,IAAA;AACP,EAAA;AACQ,EAAA;AACV;AAqBgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACE,MAAA;AACP,IAAA;AACM,IAAA;AACF,IAAA;AACG,IAAA;AACR,EAAA;AACA,EAAA;AACQ,IAAA;AACA,IAAA;AACF,IAAA;AACN,EAAA;AACS,EAAA;AACV;AnBw6IY;AACA;AoBvnKI;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACG,EAAA;AACF,IAAA;AACC,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAMa;AACF,EAAA;AACA,EAAA;AACV,EAAA;AACU,EAAA;AACD,EAAA;AACA,EAAA;AACD,EAAA;AACE,EAAA;AACV,EAAA;AACA,EAAA;AACS,EAAA;AACC,EAAA;AACX;AAiCc;AAEL;AACJ,EAAA;AACG,IAAA;AACA,IAAA;AACC,IAAA;AACR,EAAA;AACI,EAAA;AACI,EAAA;AACF,IAAA;AACJ,MAAA;AACI,IAAA;AACA,IAAA;AACJ,MAAA;AACD,IAAA;AACC,MAAA;AACF,EAAA;AACD;AAEM;AAGG;AACC,EAAA;AACJ,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACI,EAAA;AACL,EAAA;AACE,IAAA;AACA,IAAA;AACL,MAAA;AACA,MAAA;AACI,MAAA;AACJ,IAAA;AACD,EAAA;AACK,EAAA;AACG,EAAA;AACF,EAAA;AACR;AAES;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACD,EAAA;AAGT;AAOgB;AACT,EAAA;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACM,IAAA;AACC,IAAA;AACF,IAAA;AACL,EAAA;AACM,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACN,EAAA;AACOkB,IAAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AAEG,MAAA;AACL,QAAA;AACA,QAAA;AACD,MAAA;AACI,MAAA;AACJ,MAAA;AACD,IAAA;AACI,IAAA;AACG,MAAA;AACF,MAAA;AACH,QAAA;AACI,QAAA;AACL,MAAA;AACK,QAAA;AACH,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACF,MAAA;AACA,MAAA;AACD,IAAA;AACM,IAAA;AACA,IAAA;AACF,IAAA;AACC,MAAA;AACE,IAAA;AACF,MAAA;AACL,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACA,EAAA;AACC,EAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACD;AAcM;AAMU;AAKT,EAAA;AACA,EAAA;AACA,EAA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AAA;AACA,QAAA;AACG,QAAA;AACL,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAEA,IAAA;AACM,MAAA;AACL,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAAA;AAIA,IAAA;AACC,MAAA;AACD,IAAA;AACA,IAAA;AACC,MAAA;AACD,IAAA;AACA,IAAA;AACO,MAAA;AACN,MAAA;AACD,IAAA;AACA,IAAA;AACO,MAAA;AACN,MAAA;AACD,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AtBoxKY;AACA;AuB/xLC;AAEA;AAEA;AAEA;AAEA;AAEA;AAGP;AA4FA;AACA;AAGG;AACE,EAAA;AACN,EAAA;AACI,IAAA;AACR,EAAA;AACU,EAAA;AACA,EAAA;AACH,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACX;AAESC;AACF,EAAA;AACF,EAAA;AAAiB,IAAA;AAAgF,EAAA;AAC9F,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACT,EAAA;AACC,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACL,EAAA;AACH,EAAA;AACR;AAGgB;AACL,EAAA;AACH,EAAA;AACR;AAGgB;AACRA,EAAAA;AACR;AAGgB;AACL,EAAA;AACV,EAAA;AACO,EAAA;AACR;AAES;AACC,EAAA;AACV;AAES;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACJ,EAAA;AACE,EAAA;AACR;AAGS;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AACC,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACR;AAKM;AACL,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACK;AACA;AAEA;AACL,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA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AACO,EAAA;AACR;AAGgB;AACT,EAAA;AACI,EAAA;AACH,EAAA;AACR;AAES;AACC,EAAA;AACV;AAES;AACF,EAAA;AACN,EAAA;AACC,IAAA;AACK,MAAA;AACC,MAAA;AACA,MAAA;AACN,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGS;AACJ,EAAA;AACJ,EAAA;AACS,IAAA;AACF,IAAA;AACD,IAAA;AACJ,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACF,EAAA;AACJ,EAAA;AACO,IAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAEM;AAGG;AACF,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAES;AACD,EAAA;AACR;AAES;AACF,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACN,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACC,EAAA;AACA,IAAA;AACN,IAAA;AACO,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGS;AACH,EAAA;AACC,EAAA;AACI,EAAA;AACJ,EAAA;AACG,EAAA;AACF,EAAA;AACR;AAGgB;AACL,EAAA;AACH,EAAA;AACR;AAMgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACL,EAAA;AACJ,EAAA;AACK,EAAA;AACJ,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACT,EAAA;AACN,EAAA;AACO,IAAA;AACC,IAAA;AACR,EAAA;AACQ,EAAA;AACT;AAGgB;AACN,EAAA;AACV;AAGgB;AACL,EAAA;AACJ,EAAA;AACC,EAAA;AACR;AAoBgB;AACL,EAAA;AACJ,EAAA;AACN,EAAA;AACO,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACN,EAAA;AACM,IAAA;AACC,IAAA;AACN,IAAA;AACKA,MAAAA;AACE,MAAA;AACF,MAAA;AACJ,MAAA;AACA,IAAA;AACF,EAAA;AACM,EAAA;AACC,IAAA;AACA,IAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACC,EAAA;AACO,EAAA;AACV;AA6BgB;AACL,EAAA;AACJ,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACF,IAAA;AACL,EAAA;AACO,EAAA;AACF,IAAA;AACI,IAAA;AACA,IAAA;AACR,IAAA;AACI,IAAA;AACI,IAAA;AACJ,IAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACM,MAAA;AACN,MAAA;AACA,MAAA;AACI,MAAA;AACH,IAAA;AACF,IAAA;AACD,EAAA;AACD;AAYS;AACD,EAAA;AACN,IAAA;AACA,IAAA;AACQ,IAAA;AACA,IAAA;AACT,EAAA;AACD;AAEM;AACC,EAAA;AACN,EAAA;AACO,EAAA;AACR;AAES;AACR,EAAA;AACK,IAAA;AACC,IAAA;AACN,EAAA;AACD;AAMS;AACF,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACC,IAAA;AACA,IAAA;AAEN,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AAC2B,EAAA;AACxB,EAAA;AACR,IAAA;AACO,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACI,EAAA;AACJ,EAAA;AACC,EAAA;AACR;AAGS;AACFoB,EAAAA;AACAlB,EAAAA;AACIA,EAAAA;AACAA,EAAAA;AACLkB,EAAAA;AACA,IAAA;AACE,IAAA;AACC,IAAA;AACA,IAAA;AACR,EAAA;AACM,EAAA;AACC,EAAA;AACR;AAES;AACJ,EAAA;AACA,EAAA;AACC,IAAA;AACE,IAAA;AACC,IAAA;AACR,EAAA;AACU,EAAA;AACH,EAAA;AACR;AAcS;AACF,EAAA;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACD,EAAA;AAGC,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACA,IAAA;AACF,IAAA;AACH,MAAA;AACM,MAAA;AACA,IAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACOpB,IAAAA;AACA,IAAA;AACA,IAAA;AACD,IAAA;AACL,IAAA;AACD,EAAA;AACI,EAAA;AACE,EAAA;AACCqB,IAAAA;AACA,IAAA;AACC,MAAA;AACN,MAAA;AACC,QAAA;AACA,QAAA;AACI,QAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACI,QAAA;AACJ,QAAA;AACD,MAAA;AACD,IAAA;AACK,IAAA;AACEA,IAAAA;AACR,EAAA;AAEM,EAAA;AACA,EAAA;AACC,IAAA;AACF,IAAA;AACJ,IAAA;AACM,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AACAC,EAAAA;AAGA,EAAA;AACC,IAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACA,IAAA;AACL,IAAA;AACItB,IAAAA;AACG,IAAA;AACF,MAAA;AACE,MAAA;AACF,MAAA;AAEH,QAAA;AACA,QAAA;AACD,MAAA;AACI,MAAA;AACA,MAAA;AACA,MAAA;AACL,IAAA;AACO,IAAA;AACR,EAAA;AAGM,EAAA;AACC,IAAA;AACA,IAAA;AACF,IAAA;AACA,IAAA;AACE,IAAA;AACC,MAAA;AACN,MAAA;AACD,IAAA;AACME,IAAAA;AACA,IAAA;AACF,IAAA;AACE,MAAA;AACA,MAAA;AACN,IAAA;AACI,IAAA;AACE,MAAA;AACA,MAAA;AACN,IAAA;AACO,IAAA;AACR,EAAA;AAOM,EAAA;AAWC,IAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACC,IAAA;AACC,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACL,IAAA;AACIF,IAAAA;AACE,IAAA;AACN,IAAA;AACA,IAAA;AACI,IAAA;AACA,IAAA;AACAuB,IAAAA;AAEG,MAAA;AACN,MAAA;AACD,IAAA;AAEIA,IAAAA;AACG,IAAA;AACR,EAAA;AAEM,EAAA;AACC,IAAA;AACC,IAAA;AACAtB,MAAAA;AACDA,MAAAA;AACC,MAAA;AACP,IAAA;AACI,IAAA;AACJ,IAAA;AACO,MAAA;AACD,MAAA;AACL,MAAA;AACC,QAAA;AACI,QAAA;AACA,QAAA;AACL,MAAA;AACD,IAAA;AACO,IAAA;AACR,EAAA;AAEM,EAAA;AACC,IAAA;AACN,IAAA;AACO,MAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACM,MAAA;AACF,MAAA;AACA,MAAA;AACA,MAAA;AACH,QAAA;AACI,QAAA;AACL,MAAA;AACI,MAAA;AACH,QAAA;AACA,QAAA;AACI,QAAA;AACL,MAAA;AACI,MAAA;AACE,MAAA;AACA,MAAA;AACFD,MAAAA;AACE,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACI,QAAA;AACH,UAAA;AACA,UAAA;AAEC,YAAA;AACAA,YAAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACD,UAAA;AACD,QAAA;AACI,QAAA;AACJ,QAAA;AACA,QAAA;AACD,MAAA;AACI,MAAA;AACJ,MAAA;AACD,IAAA;AACD,EAAA;AAEM,EAAA;AACI,EAAA;AACV,EAAA;AAGM,EAAA;AACA,EAAA;AACN,EAAA;AACOA,IAAAA;AACA,IAAA;AACL,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACM,EAAA;AACF,EAAA;AAGE,EAAA;AACA,EAAA;AACD,EAAA;AACE,IAAA;AACA,IAAA;AACE,IAAA;AACT,EAAA;AAES,EAAA;AACF,IAAA;AACF,IAAA;AACL,EAAA;AAEQ,EAAA;AACD,IAAA;AACC,IAAA;AACP,EAAA;AACQ,EAAA;AACV;AAMS;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACC,IAAA;AACC,IAAA;AACR,EAAA;AACU,EAAA;AACJ,EAAA;AACN,EAAA;AACI,EAAA;AACJ,EAAA;AACO,IAAA;AACC,IAAA;AAAsB,IAAA;AAE9B,EAAA;AACS,EAAA;AACF,IAAA;AACA,IAAA;AACC,MAAA;AACN,MAAA;AACA,IAAA;AACG,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAMS;AACJ,EAAA;AACA,EAAA;AACH,IAAA;AACM,IAAA;AACN,IAAA;AACO,MAAA;AACC,MAAA;AACR,IAAA;AACD,EAAA;AACU,EAAA;AACA,EAAA;AACJ,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACC,IAAA;AACF,IAAA;AACC,IAAA;AACG,IAAA;AACT,EAAA;AACM,EAAA;AACA,EAAA;AACF,EAAA;AACK,EAAA;AACF,IAAA;AACN,IAAA;AACO,MAAA;AACN,MAAA;AACK,QAAA;AACJ,QAAA;AACI,QAAA;AACH,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACD,EAAA;AAEM,EAAA;AACN,EAAA;AACO,IAAA;AACE,IAAA;AACT,EAAA;AACA,EAAA;AACO,EAAA;AACR;AAES;AACE,EAAA;AACD,EAAA;AACD,EAAA;AACD,EAAA;AACR;AAKgB;AACT,EAAA;AACC,EAAA;AACR;AAMgB;AACT,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAGS;AACE,EAAA;AACJ,EAAA;AACK,EAAA;AACL,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,IAAA;AACC,MAAA;AACN,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACI,QAAA;AACH,UAAA;AACD,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACKwB,IAAAA;AACN,EAAA;AACU,EAAA;AACJ,EAAA;AACA,EAAA;AACN,EAAA;AACOxB,IAAAA;AACF,IAAA;AACA,IAAA;AACJ,IAAA;AACK,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACM,MAAA;AACP,IAAA;AACA,IAAA;AACM,IAAA;AACF,IAAA;AACG,IAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACI,MAAA;AACJ,MAAA;AACM,IAAA;AACH,IAAA;AACL,EAAA;AACM,EAAA;AACF,EAAA;AACM,EAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACN,IAAA;AACA,IAAA;AACD,EAAA;AACQ,EAAA;AACD,EAAA;AACR;AAGS;AACF,EAAA;AACG,EAAA;AACF,IAAA;AACE,IAAA;AACR,EAAA;AACF;AAgBgB;AAKX,EAAA;AACE,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AAGA,EAAA;AACA,EAAA;AACN,EAAA;AACK,IAAA;AACE,IAAA;AACD,IAAA;AACG,IAAA;AACT,EAAA;AACI,EAAA;AACG,IAAA;AACN,IAAA;AACA,IAAA;AACO,MAAA;AACC,MAAA;AACR,IAAA;AACD,EAAA;AAGQ,EAAA;AACF,EAAA;AACI,EAAA;AACH,IAAA;AACA,IAAA;AACN,IAAA;AACMA,MAAAA;AACC,MAAA;AACD,MAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,MAAA;AACC,MAAA;AACC,MAAA;AACP,IAAA;AACI,IAAA;AACH,MAAA;AACI,MAAA;AACL,IAAA;AACM,IAAA;AACA,IAAA;AACN,IAAA;AACO,MAAA;AACF,MAAA;AACC,QAAA;AACJ,QAAA;AACD,MAAA;AACI,MAAA;AACE,MAAA;AACD,MAAA;AACN,IAAA;AACI,IAAA;AACJ,IAAA;AACQ,IAAA;AACT,EAAA;AAGM,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACC,IAAA;AACD,IAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACC,MAAA;AACA,MAAA;AACL,IAAA;AACI,IAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACA,MAAA;AACI,MAAA;AACL,IAAA;AACM,IAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACA,MAAA;AACM,MAAA;AACF,MAAA;AACL,IAAA;AACI,IAAA;AACG,MAAA;AACC,MAAA;AA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EAAA;AACE,EAAA;AACJ,EAAA;AACG,EAAA;AACR;AAGS;AACF,EAAA;AACN,EAAA;AACK,IAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACN,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACF,EAAA;AACG,EAAA;AACR;AAMS;AACF,EAAA;AACF,EAAA;AACE,EAAA;AACL,IAAA;AACO,IAAA;AACR,EAAA;AACA,EAAA;AACO,IAAA;AACA0B,IAAAA;AACA,IAAA;AACD,IAAA;AACN,EAAA;AACO,EAAA;AACR;AAQS;AACF,EAAA;AACD,EAAA;AACD,EAAA;AACE,EAAA;AACG,EAAA;AACV;AAGS;AACF,EAAA;AACN,EAAA;AACM,EAAA;AACI,EAAA;AACJ,EAAA;AACF,EAAA;AACE,EAAA;AACA,EAAA;AACK,EAAA;AACX,EAAA;AACO,IAAA;AACN,IAAA;AACK,IAAA;AACN,EAAA;AACO,EAAA;AACR;AAGS;AACR,EAAA;AACO,IAAA;AACN,IAAA;AACK,MAAA;AACL,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACF,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACF,IAAA;AACD,IAAA;AACN,EAAA;AACD;AAMgB;AACL,EAAA;AACH,EAAA;AACR;AAES;AACJ,EAAA;AACG,EAAA;AACR;AAQgB;AAKL,EAAA;AACJ,EAAA;AACA,EAAA;AACG,EAAA;AACH,EAAA;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACF,EAAA;AACE,EAAA;AACF,EAAA;AACE,EAAA;AACI,EAAA;AACL,EAAA;AACC,EAAA;AACG,EAAA;AAGH,EAAA;AACE,EAAA;AACJ,EAAA;AACC,EAAA;AACE,IAAA;AACN,IAAA;AACA,IAAA;AACO,MAAA;AACD,MAAA;AACL,MAAA;AACD,IAAA;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CG,QAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACI,QAAA;AACD,QAAA;AACH,QAAA;AACA,MAAA;AACK,IAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACM,MAAA;AACP,IAAA;AACI,IAAA;AACA,IAAA;AACA,IAAA;AACG,MAAA;AACH,QAAA;AACA,QAAA;AACH,MAAA;AACK,IAAA;AACF,IAAA;AACG,MAAA;AACL,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACG,QAAA;AACA,QAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACF,EAAA;AACM,EAAA;AACR;AnC4/RY;AACA;AoC9xSF;AAEJuB;AAGG;AACD,EAAA;AACR;AAGS;AACF,EAAA;AACN,EAAA;AACKC,IAAAA;AACJ,IAAA;AACO,MAAA;AACFA,MAAAA;AACH,QAAA;AACAA,QAAAA;AACM,MAAA;AACR,IAAA;AACM,IAAA;AACP,EAAA;AACO,EAAA;AACR;AAGS;AAQJvC,EAAAA;AACA,EAAA;AACM,EAAA;AACF,IAAA;AACC,IAAA;AACT,EAAA;AACM,EAAA;AACA,EAAA;AACI,EAAA;AACJ,EAAA;AACC,EAAA;AACA,IAAA;AACE,IAAA;AACR,IAAA;AACA,IAAA;AACO,IAAA;AACJ,IAAA;AACGuC,IAAAA;AACE,IAAA;AACR,IAAA;AACAvC,IAAAA;AACA,IAAA;AACC,EAAA;AACH;AAQa;AACN,EAAA;AACA,EAAA;AACA,EAAA;AAEI,EAAA;AACH,IAAA;AACC,MAAA;AACD,MAAA;AACC,MAAA;AACN,MAAA;AACG,MAAA;AACA,MAAA;AACG,MAAA;AACN,MAAA;AACA,MAAA;AACMsC,MAAAA;AACN,MAAA;AACM,MAAA;AACN,IAAA;AAGF,EAAA;AACO,IAAA;AAED,EAAA;AACC,IAAA;AACD,IAAA;AACC,IAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACR,IAAA;AACA,IAAA;AACA,EAAA;AAED,EAAA;AACO,IAAA;AACH,MAAA;AACA,MAAA;AACH,IAAA;AAGD,EAAA;AACS,IAAA;AACF,IAAA;AACA,IAAA;AACH,MAAA;AACA,MAAA;AACA,MAAA;AACH,IAAA;AACM,IAAA;AAEC,MAAA;AAAmN,IAAA;AAGnN,MAAA;AACF,IAAA;AACN,EAAA;AAEA,EAAA;AACO,IAAA;AACE,IAAA;AACF,IAAA;AACC,MAAA;AACD,MAAA;AACC,MAAA;AACN,MAAA;AACK,MAAA;AACA,MAAA;AACL,MAAA;AACA,MAAA;AACM,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AAAQ;AAEF,MAAA;AACN,IAAA;AACK,IAAA;AACP,EAAA;AAEA,EAAA;AACO,IAAA;AACE,IAAA;AACF,IAAA;AACC,MAAA;AACD,MAAA;AACC,MAAA;AACN,MAAA;AACK,MAAA;AACA,MAAA;AACL,MAAA;AACA,MAAA;AACM,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACK,IAAA;AACN,IAAA;AACO,MAAA;AACR,EAAA;AACO,EAAA;AACR;ApCgwSY;AACA;AqC76SF;AAEJ;AAUO;AACN,EAAA;AACA,EAAA;AACA,EAAA;AAEN,EAAA;AACO,IAAA;AACA,IAAA;AAEA,IAAA;AACP,EAAA;AACA,EAAA;AACO,IAAA;AAED,IAAA;AACC,IAAA;AACC,MAAA;AACD,MAAA;AACC,MAAA;AACN,MAAA;AACK,MAAA;AACA,MAAA;AACC,MAAA;AACN,MAAA;AACA,MAAA;AACME,MAAAA;AACA,MAAA;AACN,IAAA;AACF,EAAA;AAEI,EAAA;AAAoB,IAAA;AAEhB,MAAA;AACD,MAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACA,MAAA;AACA,IAAA;AACF,EAAA;AAEA,EAAA;AACO,IAAA;AACC,MAAA;AACD,MAAA;AACC,MAAA;AACD,MAAA;AACA,MAAA;AACC,MAAA;AACN,MAAA;AACA,MAAA;AACM,MAAA;AACA,MAAA;AACA,MAAA;AACN,IAAA;AAEM,EAAA;AACD,IAAA;AAEP,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACD,MAAA;AACC,MAAA;AACN,MAAA;AACM,MAAA;AACA,MAAA;AACD,MAAA;AACL,MAAA;AACI,MAAA;AACJ,IAAA;AACK,IAAA;AACC,MAAA;AACD,MAAA;AACC,MAAA;AACN,MAAA;AACK,MAAA;AACA,MAAA;AACC,MAAA;AACN,MAAA;AACA,MAAA;AACMA,MAAAA;AACA,MAAA;AACN,IAAA;AACF,EAAA;AACO,EAAA;AACR;ArC85SY;AACA;AsCtgTF;AAKD;AACG,EAAA;AACD,EAAA;AACA,EAAA;AACF,EAAA;AACT;AAEM;AACAC;AAEG;AACF,EAAA;AACA,EAAA;AACI,EAAA;AACA,EAAA;AACD,EAAA;AACV;AAMS;AACF,EAAA;AACN,EAAA;AACO,IAAA;AACF,IAAA;AACH,MAAA;AACM,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACI,EAAA;AACJ,IAAA;AACJ,MAAA;AACG,QAAA;AACA,QAAA;AACH,MAAA;AACI,IAAA;AACJ,MAAA;AACG,QAAA;AACA,QAAA;AACA,QAAA;AACF,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACD,IAAA;AACO,MAAA;AACA,MAAA;AACL,QAAA;AACK,MAAA;AACL,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACF,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAGS;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AAEA,EAAA;AACE,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AACE,IAAA;AACA,IAAA;AACT,EAAA;AACD;AAGS;AACE,EAAA;AACJ,EAAA;AACA,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACD,MAAA;AACA,MAAA;AACC,MAAA;AACP,IAAA;AACA,IAAA;AACQ,IAAA;AACA,IAAA;AACF,EAAA;AAEA,IAAA;AACA,IAAA;AACAC,IAAAA;AACC,MAAA;AACN,MAAA;AACD,IAAA;AACA,IAAA;AACOA,IAAAA;AACCA,IAAAA;AACR,IAAA;AACD,EAAA;AACO,EAAA;AACE,IAAA;AACA,IAAA;AACR,IAAA;AACO,MAAA;AACD,MAAA;AACC,MAAA;AACN,MAAA;AACG,MAAA;AACA,MAAA;AACH3B,MAAAA;AACA,MAAA;AACA,MAAA;AACMyB,MAAAA;AACA,MAAA;AACF,MAAA;AACL,IAAA;AACD,EAAA;AACD;AASa;AACN,EAAA;AACA,EAAA;AACN,EAAA;AACS,IAAA;AACF,IAAA;AACP,EAAA;AACA,EAAA;AACO,IAAA;AACD,IAAA;AACC,IAAA;AACC,MAAA;AACD,MAAA;AACC,MAAA;AACN,MAAA;AACG,MAAA;AACG,MAAA;AACN,MAAA;AACA,MAAA;AACK,MAAA;AACL,IAAA;AACF,EAAA;AACM,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACG,IAAA;AACF,IAAA;AACA,IAAA;AACH,MAAA;AACF,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACG,QAAA;AACA,QAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACA,EAAA;AACO,IAAA;AACE,IAAA;AACF,IAAA;AACA,IAAA;AACC,IAAA;AACR,EAAA;AACO,EAAA;AACR;AtC6+SY;AACA;AuCtsTF;AAIJC;AAGG;AACJ1B,EAAAA;AACK,EAAA;AACFA,EAAAA;AAIR;AAgBS;AACC,EAAA;AACA,EAAA;AACH,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACN,IAAA;AACI,MAAA;AACA,MAAA;AACJ,IAAA;AACM,IAAA;AACE,IAAA;AACT,EAAA;AAEM,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAES;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACA,EAAA;AACJ,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACG,IAAA;AACA,IAAA;AACP,EAAA;AACMgB,EAAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACF,EAAA;AACD,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACF,IAAA;AACF,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAEa;AAGJ;AACA,EAAA;AACF,EAAA;AACA,EAAA;AACI,EAAA;AACJ,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACE,MAAA;AACA,MAAA;AACD,QAAA;AACH,QAAA;AACG,QAAA;AACH,QAAA;AACG,QAAA;AACA,QAAA;AACL,MAAA;AACA,MAAA;AACD,IAAA;AACK,IAAA;AACE,MAAA;AACAY,MAAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,QAAA;AACA,QAAA;AACAA,QAAAA;AACA,QAAA;AACA,QAAA;AACAA,QAAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACJ,MAAA;AACD,MAAA;AACD,IAAA;AACK,IAAA;AACE,MAAA;AACA,MAAA;AACD,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACJ,MAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AAEC,MAAA;AACG,QAAA;AACA,QAAA;AACH,MAAA;AACF,EAAA;AACD;AAGS;AAEF,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAGS;AACF,EAAA;AACA,EAAA;AACE,EAAA;AACT;AAUa;AACN,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACD,IAAA;AACC,IAAA;AACP,EAAA;AACA,EAAA;AACO,IAAA;AACE,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACD5B,MAAAA;AACDA,MAAAA;AACC,MAAA;AACL,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACG,QAAA;AACA,QAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACD,IAAA;AACK,IAAA;AACF,IAAA;AACG,MAAA;AACH,QAAA;AACA,QAAA;AACH,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AvC6pTY;AACA;AwCl4TN;AACK,EAAA;AACA,EAAA;AACV,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AACM;AAEA;AACA;AACA;AACA6B;AACA;AACA;AACA;AACA;AAEG;AACD,EAAA;AACR;AAEM;AAGG;AACH,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAYa;AACN,EAAA;AACA,EAAA;AACC,IAAA;AACA,IAAA;AACC,IAAA;AACR,EAAA;AAGM,EAAA;AACAjC,EAAAA;AACG,IAAA;AACF,IAAA;AACE,IAAA;AACD,IAAA;AACD,MAAA;AACL,MAAA;AACI,MAAA;AACL,IAAA;AACO,IAAA;AACR,EAAA;AACA,EAAA;AACO,IAAA;AACE,IAAA;AACFI,IAAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACFA,IAAAA;AACG,MAAA;AACR,EAAA;AAGM,EAAA;AACA,EAAA;AACA,EAAA;AACC,IAAA;AACA,IAAA;AACD,IAAA;AACC,IAAA;AACE,IAAA;AACF,IAAA;AACAwB,IAAAA;AACA,IAAA;AACC,MAAA;AACD,MAAA;AACC,MAAA;AACN,MAAA;AACG,MAAA;AACG,MAAA;AACN,MAAA;AACA,MAAA;AACA,IAAA;AACK,IAAA;AAEF,IAAA;AACA,IAAA;AAEE,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AAC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CD,EAAA;AACD;AAGgB;AACN,EAAA;AACT,EAAA;AACO,IAAA;AACE,IAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACN,EAAA;AACI,EAAA;AACK,EAAA;AACV;AAYgB;AACT,EAAA;AACD,EAAA;AACCP,EAAAA;AACC,EAAA;AACR;AAGgB;AAMT,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAEaO;AACA;AAGG;AACR,EAAA;AACR;AAGa;AAGG;AACT,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AAEE,IAAA;AACF,MAAA;AACA,QAAA;AACH,UAAA;AACD,QAAA;AACA,QAAA;AACI,MAAA;AACA,QAAA;AACH,UAAA;AACD,QAAA;AACA,QAAA;AACI,MAAA;AACA,QAAA;AACH,UAAA;AACD,QAAA;AACA,QAAA;AACI,MAAA;AACA,QAAA;AACH,UAAA;AACD,QAAA;AACA,QAAA;AACI,MAAA;AACA,QAAA;AACH,UAAA;AACD,QAAA;AACA,QAAA;AACF,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAQa;AACT,EAAA;AACH,EAAA;AACU,EAAA;AACV,EAAA;AACM,EAAA;AACA,EAAA;AACN,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACM,EAAA;AACN,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAOgB;AACP,EAAA;AACJ,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACH,EAAA;AACR;AAGgB;AACP,EAAA;AACJ,EAAA;AACG,EAAA;AACR;A7Co/UY;AACA;A8C7qVD;AACG;AACA;AACJ,EAAA;AACH,EAAA;AACC,EAAA;AACR;AACMgC;AAEO;AAOG;AACL,EAAA;AACA,EAAA;AACA,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACE,EAAA;AACF,IAAA;AACA,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACA,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACA,IAAA;AACE,MAAA;AACN,MAAA;AACC,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACK,IAAA;AACA,IAAA;AACE,MAAA;AACA,MAAA;AACL,QAAA;AACA,QAAA;AACD,MAAA;AACI,MAAA;AACJ,MAAA;AACD,IAAA;AACK,IAAA;AACA,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACA,IAAA;AACA,IAAA;AACJ,MAAA;AACI,IAAA;AACA,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACI,IAAA;AACA,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACA,IAAA;AACA,IAAA;AACJ,MAAA;AACI,IAAA;AACA,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACA,IAAA;AACJ,MAAA;AACI,IAAA;AACA,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACA,IAAA;AACJ,MAAA;AACI,IAAA;AACA,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACA,IAAA;AACA,IAAA;AACE,MAAA;AACA,MAAA;AACF,MAAA;AACA,MAAA;AACJ,MAAA;AACD,IAAA;AACK,IAAA;AACE,MAAA;AACA,MAAA;AAEN,MAAA;AACD,IAAA;AACK,IAAA;AACJ,MAAA;AACI,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACI,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACJ,MAAA;AACI,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACJ,MAAA;AACI,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACF,MAAA;AACA,MAAA;AACJ,MAAA;AACD,IAAA;AACK,IAAA;AACJ,MAAA;AACI,IAAA;AACA,IAAA;AACJ,MAAA;AACI,IAAA;AACJ,MAAA;AACD,IAAA;AACC,MAAA;AACF,EAAA;AACD;AAOgB;AACX,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAQgB;AACT,EAAA;AACL,IAAA;AACA,IAAA;AACO,IAAA;AACP,IAAA;AACD,EAAA;AACI,EAAA;AACG,EAAA;AACR;AAGa;AACH,EAAA;AACD,EAAA;AACE,EAAA;AACA,EAAA;AACX;AAGgB;AACR,EAAA;AAIR;AAGgB;AACT,EAAA;AACC,EAAA;AACR;A9CipVY;AACA;A+Ch3VChC;AACF;AAUF;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACA,EAAA;AACI,EAAA;AACD,EAAA;AACV;AAUgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACAP,EAAAA;AACAwC,EAAAA;AACC,IAAA;AACE,IAAA;AACF,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACI,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACA,IAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACR,EAAA;AACI,EAAA;AACE,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACK,EAAA;AACI,EAAA;AACL,EAAA;AACC,IAAA;AACG,IAAA;AACR,EAAA;AAGMrC,EAAAA;AACG,IAAA;AACJ,IAAA;AACG,IAAA;AACN,MAAA;AACM,MAAA;AACP,IAAA;AACO,IAAA;AACR,EAAA;AACA,EAAA;AACO,IAAA;AACE,IAAA;AACF,IAAA;AACF,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AAGA,EAAA;AACO,IAAA;AACA,IAAA;AACD,IAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACH,MAAA;AACD,IAAA;AACM,IAAA;AACCI,MAAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AAGA,EAAA;AACO,IAAA;AACE,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AAQF,IAAA;AACJ,IAAA;AACM,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACA,MAAA;AACF,MAAA;AACJ,MAAA;AACM,IAAA;AACA,MAAA;AACA,MAAA;AACP,IAAA;AACI,IAAA;AACL,EAAA;AACS,EAAA;AACA,EAAA;AACV;AAGgB;AACR,EAAA;AACR;AAGa;AAyBG;AACT,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAEM;AASN;AACO,EAAA;AACA,EAAA;AACD,EAAA;AACCf,EAAAA;AACA,EAAA;AACC,EAAA;AACA,EAAA;AACH,EAAA;AACA,EAAA;AACH,IAAA;AACO,EAAA;AACP,IAAA;AACD,EAAA;AACK,EAAA;AACC,EAAA;AACI,EAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACN,EAAA;AACK,EAAA;AACF,EAAA;AACK,IAAA;AACA,IAAA;AACT,EAAA;AACQ,EAAA;AACF,EAAA;AACG,EAAA;AACA,EAAA;AACV;AAGgB;AACT,EAAA;AAKI,EAAA;AACX;AAQA;AACO,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD,EAAA;AACA,EAAA;AACK,EAAA;AACL,EAAA;AACA,EAAA;AACI,EAAA;AACV;A/CiyVY;AACA;AgD9jWI;AACTQ,EAAAA;AACC,EAAA;AACC,IAAA;AACA,IAAA;AACPA,IAAAA;AACD,EAAA;AACD;AAgBgB;AAOT,EAAA;AACA,EAAA;AACC,IAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACEI,IAAAA;AACCA,IAAAA;AACT,EAAA;AACM,EAAA;AAGE,IAAA;AACE,IAAA;AACR,EAAA;AAEI,EAAA;AACN,EAAA;AACK,IAAA;AACA,IAAA;AACG,IAAA;AACH,IAAA;AACL,EAAA;AACK,EAAA;AACD,EAAA;AACG,IAAA;AACN,IAAA;AACM,MAAA;AACN,IAAA;AACD,EAAA;AACS,EAAA;AACV;AhDwiWY;AACA;AiDroWC;AA6BG;AACX,EAAA;AACK,EAAA;AACA,IAAA;AACA,IAAA;AACT,EAAA;AACO,EAAA;AACR;AAIa;AACF,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACX;AAMgB;AAMT,EAAA;AACA,EAAA;AAGA,EAAA;AACA,EAAA;AAGA,EAAA;AACA,EAAA;AAGA,EAAA;AACA,EAAA;AAEA,EAAA;AACA,EAAA;AAEC,EAAA;AACE,IAAA;AACA,IAAA;AACR,IAAA;AACD,EAAA;AACD;AAWgB;AAQL,EAAA;AACJ,EAAA;AACA,EAAA;AAGAqC,EAAAA;AAEA,EAAA;AAEA,EAAA;AACA,EAAA;AAEG,EAAA;AACV;AAMgB;AAQT,EAAA;AACA,EAAA;AAGA,EAAA;AACA,EAAA;AACA,EAAA;AAGA,EAAA;AACA,EAAA;AACA,EAAA;AAGA,EAAA;AACA,EAAA;AAEC,EAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACD;AAKgB;AAKT,EAAA;AACA,EAAA;AAEA,EAAA;AAEF,EAAA;AACA,EAAA;AACH,IAAA;AACD,EAAA;AACC,IAAA;AACD,EAAA;AACM,EAAA;AACC,EAAA;AACR;AAMgB;AAOT,EAAA;AACA,EAAA;AAED,EAAA;AACG,IAAA;AACR,EAAA;AAEM,EAAA;AACA,EAAA;AACA,EAAA;AAEF,EAAA;AACI,IAAA;AACR,EAAA;AAEM,EAAA;AAGA,EAAA;AAGA,EAAA;AACC,EAAA;AACN,IAAA;AACA,IAAA;AACD,EAAA;AACD;AAKgB;AACV,EAAA;AACC,EAAA;AACC,EAAA;AACR;AAKgB;AACX,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEE,EAAA;AAEL,EAAA;AAQO,IAAA;AACR,EAAA;AAEM,EAAA;AACF,EAAA;AACI,IAAA;AACR,EAAA;AAEO,EAAA;AACR;AjD6/VY;AACA;AkD3sWC;AAGA;AACZ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAGa;AACT,EAAA;AACH,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAEc;AACN,EAAA;AACC,EAAA;AACT;AAGgB;AACN,EAAA;AACD,EAAA;AACA,EAAA;AACF,EAAA;AACF,EAAA;AACG,EAAA;AACD,IAAA;AACC,IAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACL,IAAA;AACD,EAAA;AACQ,EAAA;AACC,EAAA;AACV;AAUgB;AAGT,EAAA;AACA,EAAA;AACA,EAAA;AAEA,EAAA;AAEA,EAAA;AACC,IAAA;AACA,IAAA;AACD,IAAA;AACE,IAAA;AACR,EAAA;AAEO,EAAA;AACC,IAAA;AACP,IAAA;AACA,IAAA;AACA,IAAA;AACO,MAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AACO,IAAA;AACN,MAAA;AACM,MAAA;AACN,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACQ,IAAA;AACD,MAAA;AACD,MAAA;AACC,MAAA;AACA,MAAA;AACD,QAAA;AACA,QAAA;AACL,MAAA;AACM,MAAA;AACF,MAAA;AACH,QAAA;AACD,MAAA;AAEM,MAAA;AACN,MAAA;AACI,QAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AAEA,UAAA;AACC,YAAA;AACC,cAAA;AACD,YAAA;AACD,UAAA;AACD,QAAA;AACA,MAAA;AACF,IAAA;AACD,EAAA;AACD;AAWgB;AAGR,EAAA;AACN,IAAA;AACG,IAAA;AACC,IAAA;AACJ,EAAA;AACF;AlD0qWY;AACA;AmD51WC;AAAuB;AAE5B,EAAA;AAA0B;AAEvB,EAAA;AACV,EAAA;AACD;AAKgB;AACT,EAAA;AACC,EAAA;AACR;AAcgB;AACT,EAAA;AACF,EAAA;AAEE,EAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACP,IAAA;AACO,IAAA;AACR,EAAA;AAEM,EAAA;AACC,IAAA;AACD,IAAA;AACC,IAAA;AACD,IAAA;AACE,IAAA;AACR,EAAA;AAEM,EAAA;AACC,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AAEM,EAAA;AAEE,IAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACE,IAAA;AACP,EAAA;AAEI,EAAA;AACC,IAAA;AACA,IAAA;AACN,IAAA;AACK,MAAA;AAAuB,MAAA;AAEtB,MAAA;AACN,IAAA;AACO,IAAA;AACR,EAAA;AAEO,EAAA;AAAA;AAEN,IAAA;AACI,IAAA;AACH,MAAA;AACD,IAAA;AAAA;AAEA,IAAA;AACM,MAAA;AACN,IAAA;AAAA;AAEA,IAAA;AACC,MAAA;AACD,IAAA;AAAA;AAEK,IAAA;AACJ,MAAA;AAKD,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMM,IAAA;AACL,MAAA;AACM,MAAA;AACA,MAAA;AACF,MAAA;AACE,MAAA;AACN,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAEM,IAAA;AACC,MAAA;AACA,MAAA;AACD,MAAA;AACC,MAAA;AACF,MAAA;AACH,QAAA;AACI,QAAA;AACA,QAAA;AACE,MAAA;AACH,MAAA;AACC,QAAA;AACA,QAAA;AACC,QAAA;AACN,MAAA;AACI,MAAA;AACC,QAAA;AACC,QAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAEA,IAAA;AACO,MAAA;AACD,MAAA;AACL,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAEA,IAAA;AACC,MAAA;AACD,IAAA;AAAA;AAEA,IAAA;AACC,MAAA;AACD,IAAA;AAAA;AAAA;AAAA;AAAA;AAKM,IAAA;AACC9C,MAAAA;AACDA,MAAAA;AACAA,MAAAA;AACD,MAAA;AACHA,QAAAA;AACA,QAAA;AACD,MAAA;AACM,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACNA,MAAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACD;AnDk0WY;AACA;AoDv+WN;AACA,EAAA;AACG,EAAA;AACH,IAAA;AACG,MAAA;AACN,MAAA;AACD,IAAA;AACM,IAAA;AACC,IAAA;AACR,EAAA;AACD;AAGa;AAEJ;AACF,EAAA;AACD,EAAA;AACD,EAAA;AACL;AAOgB;AACP,EAAA;AACF,EAAA;AACA,EAAA;AACAH,EAAAA;AACA,EAAA;AACF,EAAA;AACA,EAAA;AAAgB,EAAA;AACd,EAAA;AACD,IAAA;AACE,IAAA;AACD,IAAA;AACH,MAAA;AACI,QAAA;AACJ,QAAA;AACI,QAAA;AACL,MAAA;AACC,MAAA;AACI,QAAA;AACL,MAAA;AACD,IAAA;AACD,EAAA;AACK,EAAA;AACE,EAAA;AACC,IAAA;AACN,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAGA;AACS,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACAA,EAAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACD,IAAA;AACD,IAAA;AACE,IAAA;AACP,EAAA;AACD;AAMA;AACO,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACN,EAAA;AACK,EAAA;AACC,EAAA;AACA,EAAA;AACD,EAAA;AACI,EAAA;AACN,EAAA;AACE,EAAA;AACG,EAAA;AACV;AAQA;AACS,EAAA;AACF,EAAA;AACA,EAAA;AACAA,EAAAA;AACF,EAAA;AACA,EAAA;AACI,IAAA;AACA,EAAA;AACC,IAAA;AACR,IAAA;AACD,EAAA;AACM,EAAA;AACE,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD,EAAA;AACC,kBAAA;AACJ,EAAA;AACI,EAAA;AACJ,EAAA;AACK,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACC,EAAA;AAEN,EAAA;AACO,IAAA;AACT,EAAA;AACD;ApDk9WY;AACA;AqD3lXE;AACT,EAAA;AACM,EAAA;AACJ,EAAA;AACA,EAAA;AACE,EAAA;AACC,EAAA;AACA,EAAA;AACC,EAAA;AACX;AAGgB;AACT,EAAA;AACC,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACC,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACD,IAAA;AACL,EAAA;AACO,EAAA;AACF,IAAA;AACJ,IAAA;AACQ,IAAA;AACR,IAAA;AACA,IAAA;AAEA,IAAA;AACA,IAAA;AACO,IAAA;AACA,IAAA;AACF,MAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,MAAA;AACJ,MAAA;AACD,IAAA;AACQ,IAAA;AACF,MAAA;AACD,MAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACQ,IAAA;AACF,MAAA;AACC,MAAA;AACN,MAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACC,UAAA;AACA,UAAA;AACC,YAAA;AACA,YAAA;AACD,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACA,QAAA;AACA,QAAA;AACC,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AAEA,QAAA;AACC,UAAA;AACA,UAAA;AACC,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACD,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACA,QAAA;AACA,QAAA;AACC,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACD,IAAA;AACA,EAAA;AACF;ArDylXY;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/canvas/index.cjs","sourcesContent":[null,"import type {\n\tCanvasNode,\n\tCanvasNodeStyle,\n\tFanOutTrajectoryOptions,\n\tNodeTransition,\n\tTrajectoryFunction,\n\tTrajectoryPoint,\n\tXYPosition\n} from './types.js';\n\n/**\n * Standard cubic ease-out function (pure math, framework-agnostic).\n */\nexport const defaultEasing = (t: number): number => --t * t * t + 1;\n\n/**\n * Standard linear trajectory: straight line interpolation with linear scale & opacity progression.\n */\nexport const linearTrajectory: TrajectoryFunction = (\n\torigin: XYPosition,\n\ttarget: XYPosition,\n\tprogress: number\n): TrajectoryPoint => {\n\treturn {\n\t\tposition: {\n\t\t\tx: origin.x + (target.x - origin.x) * progress,\n\t\t\ty: origin.y + (target.y - origin.y) * progress\n\t\t},\n\t\tscale: progress,\n\t\topacity: Math.min(1, progress * 1.5)\n\t};\n};\n\n/**\n * Linear position trajectory: pure position interpolation without altering scale or opacity.\n */\nexport const linearPositionTrajectory: TrajectoryFunction = (\n\torigin: XYPosition,\n\ttarget: XYPosition,\n\tprogress: number\n): TrajectoryPoint => {\n\treturn {\n\t\tposition: {\n\t\t\tx: origin.x + (target.x - origin.x) * progress,\n\t\t\ty: origin.y + (target.y - origin.y) * progress\n\t\t}\n\t};\n};\n\n/**\n * Creates a fan-out trajectory where nodes arc outward from origin to target.\n */\nexport function createFanOutTrajectory(options: FanOutTrajectoryOptions = {}): TrajectoryFunction {\n\tconst {\n\t\tcurvature = 0.4,\n\t\tstartScale = 0,\n\t\tendScale = 1,\n\t\tstartOpacity = 0,\n\t\tendOpacity = 1\n\t} = options;\n\n\treturn (\n\t\torigin: XYPosition,\n\t\ttarget: XYPosition,\n\t\tprogress: number,\n\t\tindex: number,\n\t\ttotal: number\n\t): TrajectoryPoint => {\n\t\tconst dx = target.x - origin.x;\n\t\tconst dy = target.y - origin.y;\n\n\t\t// Normalized index from -1 (top) to +1 (bottom)\n\t\tconst normIndex = total > 1 ? (index - (total - 1) / 2) / ((total - 1) / 2) : 0;\n\n\t\t// Perpendicular control point offset to create smooth fan arc\n\t\tconst midX = origin.x + dx * 0.4;\n\t\tconst midY = origin.y + dy * 0.1 - normIndex * Math.abs(dx) * 0.15 * curvature;\n\n\t\t// Quadratic Bezier interpolation: B(t) = (1-t)^2 * P0 + 2(1-t)t * P1 + t^2 * P2\n\t\tconst t = progress;\n\t\tconst oneMinusT = 1 - t;\n\n\t\tconst x = oneMinusT * oneMinusT * origin.x + 2 * oneMinusT * t * midX + t * t * target.x;\n\t\tconst y = oneMinusT * oneMinusT * origin.y + 2 * oneMinusT * t * midY + t * t * target.y;\n\n\t\tconst scale = startScale + (endScale - startScale) * progress;\n\t\tconst opacity = startOpacity + (endOpacity - startOpacity) * progress;\n\n\t\treturn {\n\t\t\tposition: { x, y },\n\t\t\tscale,\n\t\t\topacity\n\t\t};\n\t};\n}\n\n/**\n * Creates a custom quadratic Bezier trajectory with a specified control point resolver.\n */\nexport function createBezierTrajectory(\n\tgetControlPoint: (origin: XYPosition, target: XYPosition) => XYPosition\n): TrajectoryFunction {\n\treturn (origin: XYPosition, target: XYPosition, progress: number): TrajectoryPoint => {\n\t\tconst cp = getControlPoint(origin, target);\n\t\tconst t = progress;\n\t\tconst oneMinusT = 1 - t;\n\n\t\tconst x = oneMinusT * oneMinusT * origin.x + 2 * oneMinusT * t * cp.x + t * t * target.x;\n\t\tconst y = oneMinusT * oneMinusT * origin.y + 2 * oneMinusT * t * cp.y + t * t * target.y;\n\n\t\treturn {\n\t\t\tposition: { x, y },\n\t\t\tscale: progress,\n\t\t\topacity: progress\n\t\t};\n\t};\n}\n\nimport { realClock, type Clock } from './clock.js';\n\n/**\n * Returns a copy of `style` with its opacity set to `opacity`.\n *\n * Supports both CSS strings (`\"color: red; opacity: 0.5;\"`, used by SvelteFlow)\n * and style objects (`{ color: 'red', opacity: 0.5 }`, used by React Flow / xyflow).\n * When `style` is empty, `emptyFormat` decides which representation is produced.\n * Never mutates the input.\n */\nexport function withStyleOpacity(\n\tstyle: CanvasNodeStyle | undefined | null,\n\topacity: number,\n\temptyFormat: 'string' | 'object' = 'string'\n): CanvasNodeStyle {\n\tif (style !== null && typeof style === 'object') {\n\t\treturn { ...style, opacity };\n\t}\n\tif (typeof style === 'string' && style.trim() !== '') {\n\t\tconst rest = style\n\t\t\t.replace(/(^|;)\\s*opacity\\s*:\\s*[^;]*;?/gi, '$1')\n\t\t\t.trim()\n\t\t\t.replace(/;+\\s*$/, '')\n\t\t\t.trim();\n\t\treturn `${rest ? rest + '; ' : ''}opacity: ${opacity};`;\n\t}\n\treturn emptyFormat === 'object' ? { opacity } : `opacity: ${opacity};`;\n}\n\nexport interface MultiNodeTransitionOptions<TNode extends CanvasNode = CanvasNode> {\n\tdefaultDuration?: number;\n\tdefaultEasing?: (t: number) => number;\n\tclock?: Clock;\n\t/**\n\t * Style representation to create for nodes that have no `style` yet when an opacity\n\t * is applied. Defaults to `'string'` (SvelteFlow); pass `'object'` for React Flow.\n\t * Existing styles always keep their representation.\n\t */\n\tstyleFormat?: 'string' | 'object';\n\t/**\n\t * Called on every frame with updated copies of the transitioning nodes, in the same\n\t * order as the `transitions` array. Input nodes are never mutated.\n\t */\n\tonUpdate?: (nodes: TNode[]) => void;\n\t/** Called once all transitions finished, with the final node copies. */\n\tonComplete?: (nodes: TNode[]) => void;\n}\n\n/**\n * Coordinates and animates multiple node transitions simultaneously.\n *\n * The input nodes are treated as immutable: every frame produces updated copies\n * (position, `data.scale` / `data.opacity`, and `style` opacity) which are handed to\n * `onUpdate` and finally `onComplete`. Merge them into your node state by `id`.\n */\nexport function runMultiNodeTransition<TNode extends CanvasNode = CanvasNode>(\n\ttransitions: NodeTransition<TNode>[],\n\toptions: MultiNodeTransitionOptions<TNode> = {}\n): () => void {\n\tif (transitions.length === 0) {\n\t\toptions.onComplete?.([]);\n\t\treturn () => {};\n\t}\n\n\tconst {\n\t\tdefaultDuration = 400,\n\t\tdefaultEasing: easingFunc = defaultEasing,\n\t\tclock = realClock,\n\t\tstyleFormat = 'string',\n\t\tonUpdate,\n\t\tonComplete\n\t} = options;\n\n\tlet isCancelled = false;\n\tconst startTime = clock.now();\n\n\tconst items = transitions.map((tr, i) => ({\n\t\t...tr,\n\t\tindex: tr.index ?? i,\n\t\ttotal: tr.total ?? transitions.length,\n\t\tduration: tr.duration ?? defaultDuration,\n\t\teasing: tr.easing ?? easingFunc,\n\t\tdelay: tr.delay ?? 0,\n\t\ttrajectory: tr.trajectory ?? linearTrajectory\n\t}));\n\n\t// Latest copy of every node; starts out as the untouched inputs.\n\tconst current: TNode[] = items.map((item) => item.node);\n\n\tfunction frame() {\n\t\tif (isCancelled) return;\n\n\t\tconst now = clock.now();\n\t\tlet allCompleted = true;\n\n\t\tfor (let i = 0; i < items.length; i++) {\n\t\t\tconst item = items[i];\n\t\t\tconst elapsed = now - (startTime + item.delay);\n\n\t\t\tif (elapsed < 0) {\n\t\t\t\tallCompleted = false;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Guard zero duration and division by zero (referenced in GOTCHAS.md)\n\t\t\tconst rawProgress = item.duration <= 0 ? 1 : Math.min(elapsed / item.duration, 1);\n\t\t\tconst progress = item.duration <= 0 ? 1 : item.easing(rawProgress);\n\n\t\t\tconst point = item.trajectory(item.from, item.to, progress, item.index, item.total);\n\n\t\t\tconst next: TNode = { ...item.node, position: { ...point.position } };\n\n\t\t\tif (point.scale !== undefined || point.opacity !== undefined) {\n\t\t\t\tconst data: Record<string, any> = { ...(item.node.data ?? {}) };\n\t\t\t\tif (point.scale !== undefined) {\n\t\t\t\t\tdata.scale = point.scale;\n\t\t\t\t}\n\t\t\t\tif (point.opacity !== undefined) {\n\t\t\t\t\tdata.opacity = point.opacity;\n\t\t\t\t\t(next as CanvasNode).style = withStyleOpacity(item.node.style, point.opacity, styleFormat);\n\t\t\t\t}\n\t\t\t\t(next as CanvasNode).data = data;\n\t\t\t}\n\n\t\t\tcurrent[i] = next;\n\n\t\t\tif (rawProgress < 1) {\n\t\t\t\tallCompleted = false;\n\t\t\t}\n\t\t}\n\n\t\tonUpdate?.(current.slice());\n\n\t\tif (!allCompleted) {\n\t\t\tclock.frame().then(frame);\n\t\t} else {\n\t\t\tonComplete?.(current.slice());\n\t\t}\n\t}\n\n\tframe();\n\n\treturn () => {\n\t\tisCancelled = true;\n\t};\n}\n","import dagre from '@dagrejs/dagre';\nimport type {\n\tCanvasEdge,\n\tCanvasNode,\n\tDirectionalDisplacementOptions,\n\tRect,\n\tReflowDisplacementOptions,\n\tXYPosition\n} from './types.js';\n\n/**\n * Calculates a bounding rectangle for a set of nodes if not provided by getNodesBounds.\n */\nexport function computeNodesBoundingBox<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\tdefaultWidth = 200,\n\tdefaultHeight = 60\n): Rect {\n\tif (nodes.length === 0) {\n\t\treturn { x: 0, y: 0, width: 0, height: 0 };\n\t}\n\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\n\tfor (const node of nodes) {\n\t\tconst width = node.measured?.width ?? (node as any).width ?? defaultWidth;\n\t\tconst height = node.measured?.height ?? (node as any).height ?? defaultHeight;\n\n\t\tminX = Math.min(minX, node.position.x);\n\t\tminY = Math.min(minY, node.position.y);\n\t\tmaxX = Math.max(maxX, node.position.x + width);\n\t\tmaxY = Math.max(maxY, node.position.y + height);\n\t}\n\n\treturn {\n\t\tx: minX,\n\t\ty: minY,\n\t\twidth: maxX - minX,\n\t\theight: maxY - minY\n\t};\n}\n\n/**\n * Pure directional displacement calculation.\n * Shifts downstream or surrounding nodes along an axis to clear space for newly inserted child nodes.\n */\nexport function calculateDirectionalDisplacement<TNode extends CanvasNode = CanvasNode>(params: {\n\texistingNodes: TNode[];\n\toriginNode: TNode;\n\tchildNodes: TNode[];\n\tgetNodesBounds?: (nodes: TNode[]) => Rect;\n\toptions?: DirectionalDisplacementOptions;\n}): Map<string, XYPosition> {\n\tconst {\n\t\texistingNodes,\n\t\toriginNode,\n\t\tchildNodes,\n\t\tgetNodesBounds,\n\t\toptions = {}\n\t} = params;\n\n\tconst {\n\t\tdirection = 'right',\n\t\tgap = 40,\n\t\tspreadSiblingLanes = false,\n\t\tsiblingTolerance = 50,\n\t\tminY\n\t} = options;\n\n\tconst displacedMap = new Map<string, XYPosition>();\n\tif (childNodes.length === 0 || existingNodes.length === 0) {\n\t\treturn displacedMap;\n\t}\n\n\tconst childBounds = getNodesBounds\n\t\t? getNodesBounds(childNodes)\n\t\t: computeNodesBoundingBox(childNodes);\n\n\tconst originWidth = originNode.measured?.width ?? (originNode as any).width ?? 200;\n\tconst originHeight = originNode.measured?.height ?? (originNode as any).height ?? 60;\n\n\t// The delta space required along primary axis\n\tconst deltaPrimary =\n\t\tdirection === 'right' || direction === 'left'\n\t\t\t? childBounds.width + gap\n\t\t\t: childBounds.height + gap;\n\n\t// Vertical displacement parameters: upward and downward row clearance\n\tconst deltaUp = Math.max(0, originNode.position.y - childBounds.y + gap);\n\tconst deltaDown = Math.max(\n\t\t0,\n\t\tchildBounds.y + childBounds.height - (originNode.position.y + originHeight) + gap\n\t);\n\n\t// Both above and below displace equally so the row expands symmetrically\n\tconst equalDelta = Math.max(deltaUp, deltaDown);\n\n\tfor (const node of existingNodes) {\n\t\tif (node.id === originNode.id) continue;\n\n\t\tconst isSiblingRow =\n\t\t\tMath.abs(node.position.y - originNode.position.y) < siblingTolerance;\n\n\t\tswitch (direction) {\n\t\t\tcase 'right': {\n\t\t\t\tif (node.position.x > originNode.position.x) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x + deltaPrimary,\n\t\t\t\t\t\ty: node.position.y\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'left': {\n\t\t\t\tif (node.position.x < originNode.position.x) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x - deltaPrimary,\n\t\t\t\t\t\ty: node.position.y\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'down': {\n\t\t\t\tif (node.position.y > originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y + deltaPrimary\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'up': {\n\t\t\t\tif (node.position.y < originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y - deltaPrimary\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'vertical': {\n\t\t\t\tif (isSiblingRow) break;\n\n\t\t\t\tif (node.position.y < originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y - equalDelta\n\t\t\t\t\t});\n\t\t\t\t} else if (node.position.y > originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y + equalDelta\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Sibling lane spreading to avoid overlap among child nodes\n\tif (spreadSiblingLanes && (direction === 'right' || direction === 'left')) {\n\t\tfor (const [nodeId, pos] of displacedMap.entries()) {\n\t\t\tconst node = existingNodes.find((n) => n.id === nodeId);\n\t\t\tif (node && Math.abs(node.position.y - originNode.position.y) < siblingTolerance) {\n\t\t\t\tconst isAbove = node.position.y < originNode.position.y;\n\t\t\t\tdisplacedMap.set(nodeId, {\n\t\t\t\t\tx: pos.x,\n\t\t\t\t\ty: pos.y + (isAbove ? -deltaUp : deltaDown)\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Floor constraint\n\tif (minY !== undefined) {\n\t\tfor (const [nodeId, pos] of displacedMap.entries()) {\n\t\t\tif (pos.y < minY) {\n\t\t\t\tdisplacedMap.set(nodeId, { x: pos.x, y: minY });\n\t\t\t}\n\t\t}\n\t}\n\n\treturn displacedMap;\n}\n\n/**\n * Calculates Dagre reflow displacement preserving the position of an anchor node.\n */\nexport function calculateDagreReflowDisplacement<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(params: {\n\tcurrentNodes: TNode[];\n\tcurrentEdges: TEdge[];\n\tfutureNodes: TNode[];\n\tfutureEdges: TEdge[];\n\toptions: ReflowDisplacementOptions;\n\tgetNodesBounds?: (nodes: TNode[]) => Rect;\n}): Map<string, XYPosition> {\n\tconst {\n\t\tcurrentNodes,\n\t\tfutureNodes,\n\t\tfutureEdges,\n\t\toptions\n\t} = params;\n\n\tconst {\n\t\tanchorNodeId,\n\t\tdirection = 'LR',\n\t\tranksep = 100,\n\t\tnodesep = 25,\n\t\tdefaultNodeWidth = 200,\n\t\tdefaultNodeHeight = 60\n\t} = options;\n\n\tconst anchorNode = currentNodes.find((n) => n.id === anchorNodeId);\n\tif (!anchorNode) return new Map();\n\n\tconst g = new dagre.graphlib.Graph();\n\tg.setDefaultEdgeLabel(() => ({}));\n\tg.setGraph({ rankdir: direction, ranksep, nodesep });\n\n\tconst nodeIds = new Set(futureNodes.map((n) => n.id));\n\n\tfutureNodes.forEach((node) => {\n\t\tconst nodeWidth = node.measured?.width ?? (node as any).width ?? defaultNodeWidth;\n\t\tconst nodeHeight = node.measured?.height ?? (node as any).height ?? defaultNodeHeight;\n\t\tg.setNode(node.id, { width: nodeWidth, height: nodeHeight });\n\t});\n\n\tfutureEdges.forEach((edge) => {\n\t\tif (nodeIds.has(edge.source) && nodeIds.has(edge.target)) {\n\t\t\tg.setEdge(edge.source, edge.target);\n\t\t}\n\t});\n\n\tdagre.layout(g);\n\n\tconst anchorLayoutPos = g.node(anchorNodeId);\n\tif (!anchorLayoutPos) return new Map();\n\n\tconst anchorWidth = anchorNode.measured?.width ?? (anchorNode as any).width ?? defaultNodeWidth;\n\tconst anchorHeight = anchorNode.measured?.height ?? (anchorNode as any).height ?? defaultNodeHeight;\n\n\tconst anchorLayoutUpperLeft = {\n\t\tx: anchorLayoutPos.x - anchorWidth / 2,\n\t\ty: anchorLayoutPos.y - anchorHeight / 2\n\t};\n\n\tconst deltaX = anchorNode.position.x - anchorLayoutUpperLeft.x;\n\tconst deltaY = anchorNode.position.y - anchorLayoutUpperLeft.y;\n\n\tconst displacementMap = new Map<string, XYPosition>();\n\n\tcurrentNodes.forEach((node) => {\n\t\tif (node.id === anchorNodeId) return;\n\n\t\tconst layoutNode = g.node(node.id);\n\t\tif (!layoutNode) return;\n\n\t\tconst nodeWidth = node.measured?.width ?? (node as any).width ?? defaultNodeWidth;\n\t\tconst nodeHeight = node.measured?.height ?? (node as any).height ?? defaultNodeHeight;\n\n\t\tconst upperLeftX = layoutNode.x - nodeWidth / 2 + deltaX;\n\t\tconst upperLeftY = layoutNode.y - nodeHeight / 2 + deltaY;\n\n\t\tif (\n\t\t\tMath.abs(upperLeftX - node.position.x) > 1 ||\n\t\t\tMath.abs(upperLeftY - node.position.y) > 1\n\t\t) {\n\t\t\tdisplacementMap.set(node.id, { x: upperLeftX, y: upperLeftY });\n\t\t}\n\t});\n\n\treturn displacementMap;\n}\n","import type {\n\tCanvasClipboardPayload,\n\tCanvasEdge,\n\tCanvasNode,\n\tXYPosition\n} from './types.js';\n\n/**\n * Checks if an event originated from a text input, textarea, contenteditable element, or rich editor.\n */\nexport function isEventFromTextInput(event: Event | { target: EventTarget | null }): boolean {\n\tconst target = (event as any).target as HTMLElement | null;\n\tif (!target) return false;\n\tif (\n\t\ttarget.tagName === 'INPUT' ||\n\t\ttarget.tagName === 'TEXTAREA' ||\n\t\ttarget.tagName === 'SELECT' ||\n\t\ttarget.isContentEditable ||\n\t\ttarget.closest?.('[contenteditable=\"true\"]') ||\n\t\ttarget.closest?.('.ProseMirror')\n\t) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/**\n * Validates that an object conforms to minimal CanvasNode shape:\n * a string `id` and a `position` whose `x` / `y` are finite numbers.\n */\nexport function isValidCanvasNode(obj: unknown): obj is CanvasNode {\n\tif (typeof obj !== 'object' || obj === null) return false;\n\tconst candidate = obj as { id?: unknown; position?: unknown };\n\tif (typeof candidate.id !== 'string') return false;\n\tconst position = candidate.position as { x?: unknown; y?: unknown } | null | undefined;\n\treturn (\n\t\ttypeof position === 'object' &&\n\t\tposition !== null &&\n\t\ttypeof position.x === 'number' &&\n\t\tNumber.isFinite(position.x) &&\n\t\ttypeof position.y === 'number' &&\n\t\tNumber.isFinite(position.y)\n\t);\n}\n\n/**\n * Validates that an object conforms to minimal CanvasEdge shape (string `id`, `source`, `target`).\n */\nexport function isValidCanvasEdge(obj: unknown): obj is CanvasEdge {\n\tif (typeof obj !== 'object' || obj === null) return false;\n\tconst candidate = obj as { id?: unknown; source?: unknown; target?: unknown };\n\treturn (\n\t\ttypeof candidate.id === 'string' &&\n\t\ttypeof candidate.source === 'string' &&\n\t\ttypeof candidate.target === 'string'\n\t);\n}\n\n/**\n * Checks if a string is a valid HTTP/HTTPS URL.\n */\nexport function isValidUrl(url: string): boolean {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\treturn parsed.protocol === 'http:' || parsed.protocol === 'https:';\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Serializes nodes and optional edges into clipboard JSON payload.\n */\nexport function serializeCanvasNodes<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(nodes: TNode[], edges?: TEdge[]): string {\n\tconst payload: CanvasClipboardPayload<TNode, TEdge> = {\n\t\tversion: 1,\n\t\tnodes,\n\t\tedges\n\t};\n\treturn JSON.stringify(payload);\n}\n\n/**\n * Parses raw JSON string into a CanvasClipboardPayload if valid.\n */\nexport function parseCanvasClipboardData<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(raw: string): CanvasClipboardPayload<TNode, TEdge> | null {\n\ttry {\n\t\tconst parsed = JSON.parse(raw);\n\t\tif (\n\t\t\ttypeof parsed === 'object' &&\n\t\t\tparsed !== null &&\n\t\t\tArray.isArray(parsed.nodes) &&\n\t\t\tparsed.nodes.length > 0 &&\n\t\t\tparsed.nodes.every(isValidCanvasNode)\n\t\t) {\n\t\t\t// Untrusted input: drop malformed edges instead of crashing downstream consumers.\n\t\t\tconst edges = Array.isArray(parsed.edges)\n\t\t\t\t? (parsed.edges as unknown[]).filter(isValidCanvasEdge)\n\t\t\t\t: undefined;\n\t\t\treturn { ...parsed, edges } as CanvasClipboardPayload<TNode, TEdge>;\n\t\t}\n\t} catch {\n\t\t// Not JSON or invalid payload\n\t}\n\treturn null;\n}\n\n/**\n * Generates unique new IDs for pasted nodes and updates edge references accordingly.\n *\n * - Nodes without a valid id / finite numeric position are skipped.\n * - `parentId` (and legacy `parentNode`) references to other pasted nodes are remapped to\n * the new IDs; such children keep their parent-relative position (no extra offset).\n * References to parents outside the pasted set are left unchanged.\n * - Edges are kept only when both endpoints were pasted; a non-array `edges` is ignored.\n */\nexport function remapPastedNodes<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[] | null | undefined = [],\n\toffset: XYPosition = { x: 30, y: 30 },\n\tidGenerator: () => string = () => `node-${Math.random().toString(36).substring(2, 9)}`\n): { nodes: TNode[]; edges: TEdge[] } {\n\tconst validNodes = (Array.isArray(nodes) ? nodes : []).filter(isValidCanvasNode) as TNode[];\n\tconst validEdges = (Array.isArray(edges) ? edges : []).filter(isValidCanvasEdge) as TEdge[];\n\n\tconst dx = Number.isFinite(offset?.x) ? offset.x : 0;\n\tconst dy = Number.isFinite(offset?.y) ? offset.y : 0;\n\n\tconst idMap = new Map<string, string>();\n\tfor (const node of validNodes) {\n\t\tidMap.set(node.id, idGenerator());\n\t}\n\n\tconst newNodes = validNodes.map((node) => {\n\t\tconst next: CanvasNode = {\n\t\t\t...node,\n\t\t\tid: idMap.get(node.id)!,\n\t\t\tposition: {\n\t\t\t\tx: node.position.x + dx,\n\t\t\t\ty: node.position.y + dy\n\t\t\t},\n\t\t\tselected: true\n\t\t};\n\n\t\tlet parentRemapped = false;\n\t\tfor (const key of ['parentId', 'parentNode'] as const) {\n\t\t\tconst parent = (node as CanvasNode)[key];\n\t\t\tif (typeof parent === 'string' && idMap.has(parent)) {\n\t\t\t\tnext[key] = idMap.get(parent)!;\n\t\t\t\tparentRemapped = true;\n\t\t\t}\n\t\t}\n\t\tif (parentRemapped) {\n\t\t\t// Position is relative to the (also pasted, also offset) parent.\n\t\t\tnext.position = { x: node.position.x, y: node.position.y };\n\t\t}\n\n\t\treturn next as TNode;\n\t});\n\n\tconst newEdges = validEdges\n\t\t.filter((e) => idMap.has(e.source) && idMap.has(e.target))\n\t\t.map((edge) => ({\n\t\t\t...edge,\n\t\t\tid: `edge-${Math.random().toString(36).substring(2, 9)}`,\n\t\t\tsource: idMap.get(edge.source)!,\n\t\t\ttarget: idMap.get(edge.target)!,\n\t\t\tselected: false\n\t\t}));\n\n\treturn { nodes: newNodes, edges: newEdges };\n}\n\n/**\n * Repositions nodes so their bounding box origin matches the given target position.\n */\nexport function repositionNodesTo<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\ttargetOrigin: XYPosition\n): TNode[] {\n\tif (nodes.length === 0) return [];\n\tconst minX = Math.min(...nodes.map((n) => n.position.x));\n\tconst minY = Math.min(...nodes.map((n) => n.position.y));\n\tconst deltaX = targetOrigin.x - minX;\n\tconst deltaY = targetOrigin.y - minY;\n\n\treturn nodes.map((n) => ({\n\t\t...n,\n\t\tposition: {\n\t\t\tx: n.position.x + deltaX,\n\t\t\ty: n.position.y + deltaY\n\t\t}\n\t}));\n}\n","import type { CanvasEdge, CanvasNode, Rect } from './types.js';\n\nexport interface CanvasHeightOptions {\n\t/** Smallest height returned (default 320). */\n\tmin?: number;\n\t/** Largest height returned; taller content is scaled/zoomed (default 560). */\n\tmax?: number;\n\t/** Extra padding around the content (default 48). */\n\tpadding?: number;\n}\n\nfunction clampHeight(contentHeight: number, options: CanvasHeightOptions = {}): number {\n\tconst { min = 320, max = 560, padding = 48 } = options;\n\treturn Math.round(Math.min(max, Math.max(min, contentHeight + padding)));\n}\n\n/**\n * Calculates the bounding box extent of all positioned nodes, or `null` for an empty graph.\n */\nexport function layoutBounds<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[]\n): Rect | null {\n\tif (nodes.length === 0) return null;\n\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\n\tfor (const node of nodes) {\n\t\tconst x = node.position.x;\n\t\tconst y = node.position.y;\n\t\tconst width = node.width ?? 180;\n\t\tconst height = node.height ?? 80;\n\n\t\tminX = Math.min(minX, x);\n\t\tminY = Math.min(minY, y);\n\t\tmaxX = Math.max(maxX, x + width);\n\t\tmaxY = Math.max(maxY, y + height);\n\t}\n\n\tif (minX === Infinity) return null;\n\treturn { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n}\n\n/**\n * Returns a clamped container height that fits the layout at 1:1 scale without overflow.\n */\nexport function canvasHeight<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\toptions?: CanvasHeightOptions\n): number {\n\treturn clampHeight(layoutBounds(nodes)?.height ?? 0, options);\n}\n\nconst DEFAULT_RANK_HEIGHT = 80 + 72; // Average node height + default rank separation\nconst NODES_PER_RANK = 2;\n\n/**\n * Estimates the container height to reserve before layout calculation finishes,\n * based on node and edge counts alone. Prevents layout shift (CLS).\n */\nexport function estimateCanvasHeight<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[],\n\toptions?: CanvasHeightOptions\n): number {\n\tconst ranks = Math.max(1, Math.ceil(nodes.length / NODES_PER_RANK));\n\treturn clampHeight(ranks * DEFAULT_RANK_HEIGHT, options);\n}\n\nfunction mix(a: number, b: number, t: number): number {\n\treturn a + (b - a) * t;\n}\n\n/**\n * Interpolates node positions and sizes between two layouts partway at progress `t` (0 = from, 1 = to).\n * Nodes present in both interpolate smoothly; nodes newly added in `to` snap to target.\n */\nexport function blendLayout<TNode extends CanvasNode = CanvasNode>(\n\tfrom: TNode[],\n\tto: TNode[],\n\tt: number\n): TNode[] {\n\tif (t >= 1) return to;\n\n\tconst fromMap = new Map<string, TNode>(from.map((n) => [n.id, n]));\n\n\treturn to.map((target) => {\n\t\tconst origin = fromMap.get(target.id);\n\t\tif (!origin) return target;\n\n\t\treturn {\n\t\t\t...target,\n\t\t\tposition: {\n\t\t\t\tx: mix(origin.position.x, target.position.x, t),\n\t\t\t\ty: mix(origin.position.y, target.position.y, t)\n\t\t\t},\n\t\t\twidth:\n\t\t\t\torigin.width !== undefined && target.width !== undefined\n\t\t\t\t\t? mix(origin.width, target.width, t)\n\t\t\t\t\t: target.width,\n\t\t\theight:\n\t\t\t\torigin.height !== undefined && target.height !== undefined\n\t\t\t\t\t? mix(origin.height, target.height, t)\n\t\t\t\t\t: target.height\n\t\t};\n\t});\n}\n\n/**\n * Filters out nodes that are not touched by any edge.\n *\n * During LLM streaming, nodes often arrive a chunk or token before the edge\n * that connects them. Displaying them immediately causes them to be placed as root\n * nodes only to jump into their correct position once the edge arrives.\n * Hiding orphan nodes during active streaming keeps the layout calm and jitter-free.\n */\nexport function connectedOnly<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[]\n): TNode[] {\n\tif (edges.length === 0) return nodes;\n\n\tconst linkedNodeIds = new Set<string>();\n\tfor (const edge of edges) {\n\t\tlinkedNodeIds.add(edge.source);\n\t\tlinkedNodeIds.add(edge.target);\n\t}\n\n\tconst filtered = nodes.filter((node) => linkedNodeIds.has(node.id));\n\t// If filtering would remove everything, return the original nodes\n\treturn filtered.length > 0 ? filtered : nodes;\n}\n","import { realClock, type Clock } from './clock.js';\nimport { ensureLayout } from './layout.js';\nimport { linearPositionTrajectory, runMultiNodeTransition } from './trajectory.js';\nimport type { CanvasEdge, CanvasNode, IncrementalLayoutOptions, NodeTransition, XYPosition } from './types.js';\nimport { connectedOnly } from './view.js';\n\nexport interface StreamingLayoutManagerOptions {\n\tthrottleMs?: number;\n\tglideDurationMs?: number;\n\tconnectedOnly?: boolean;\n\tclock?: Clock;\n\tlayoutOptions?: IncrementalLayoutOptions;\n\tonLayoutUpdated?: (positions: Map<string, XYPosition>) => void;\n}\n\n/**\n * Manages throttled incremental layout recalculation and smooth tweened node gliding\n * during active streaming (e.g. LLM streaming or graph generation).\n *\n * - `pushStreamUpdate` is a true throttle (leading + trailing edge): the first update\n * lays out immediately, further updates within `throttleMs` are coalesced and the\n * latest one is laid out when the window closes. Layouts therefore keep running at\n * most once per `throttleMs` while a stream is active, instead of being postponed\n * until the stream goes quiet.\n * - Every layout is computed from the graph (respecting `layoutOptions.storedPositions`\n * as pinned/manual coordinates) and each known node glides from its *current*\n * position (the `currentPositions` argument) to its newly computed position.\n */\nexport class StreamingLayoutManager<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n> {\n\tprivate throttleMs: number;\n\tprivate glideDurationMs: number;\n\tprivate connectedOnly: boolean;\n\tprivate clock: Clock;\n\tprivate layoutOptions: IncrementalLayoutOptions;\n\tprivate onLayoutUpdated?: (positions: Map<string, XYPosition>) => void;\n\n\tprivate pendingTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate pendingArgs: [TNode[], TEdge[], Map<string, XYPosition>] | null = null;\n\tprivate activeTransitionCancel: (() => void) | null = null;\n\tprivate targetPositions = new Map<string, XYPosition>();\n\tprivate livePositions = new Map<string, XYPosition>();\n\n\tconstructor(options: StreamingLayoutManagerOptions = {}) {\n\t\tthis.throttleMs = options.throttleMs ?? 200;\n\t\tthis.glideDurationMs = options.glideDurationMs ?? 300;\n\t\tthis.connectedOnly = options.connectedOnly ?? false;\n\t\tthis.clock = options.clock ?? realClock;\n\t\tthis.layoutOptions = options.layoutOptions ?? { direction: 'LR' };\n\t\tthis.onLayoutUpdated = options.onLayoutUpdated;\n\t}\n\n\t/**\n\t * Feed incoming nodes and edges into the streaming manager.\n\t * Throttles rapid streaming bursts (leading + trailing edge) and triggers smooth layout gliding.\n\t */\n\tpublic pushStreamUpdate(\n\t\tnodes: TNode[],\n\t\tedges: TEdge[],\n\t\tcurrentPositions: Map<string, XYPosition>\n\t): void {\n\t\tif (this.pendingTimer !== null) {\n\t\t\t// Inside the throttle window: remember the latest update for the trailing edge.\n\t\t\tthis.pendingArgs = [nodes, edges, currentPositions];\n\t\t\treturn;\n\t\t}\n\n\t\t// Leading edge: lay out immediately and open a throttle window.\n\t\tthis.recalculateAndGlide(nodes, edges, currentPositions);\n\t\tthis.startWindow();\n\t}\n\n\tprivate startWindow(): void {\n\t\tthis.pendingTimer = setTimeout(() => {\n\t\t\tthis.pendingTimer = null;\n\t\t\tconst args = this.pendingArgs;\n\t\t\tthis.pendingArgs = null;\n\t\t\tif (args) {\n\t\t\t\t// Trailing edge: lay out the latest coalesced update and keep throttling.\n\t\t\t\tthis.recalculateAndGlide(...args);\n\t\t\t\tthis.startWindow();\n\t\t\t}\n\t\t}, Math.max(0, this.throttleMs));\n\t}\n\n\t/**\n\t * Forces immediate calculation and cancels pending throttled timers.\n\t */\n\tpublic flush(\n\t\tnodes: TNode[],\n\t\tedges: TEdge[],\n\t\tcurrentPositions: Map<string, XYPosition>\n\t): void {\n\t\tthis.clearPending();\n\t\tthis.recalculateAndGlide(nodes, edges, currentPositions);\n\t}\n\n\t/**\n\t * Latest computed layout targets (the positions nodes are gliding towards).\n\t */\n\tpublic getTargetPositions(): Map<string, XYPosition> {\n\t\treturn new Map(this.targetPositions);\n\t}\n\n\tprivate clearPending(): void {\n\t\tif (this.pendingTimer !== null) {\n\t\t\tclearTimeout(this.pendingTimer);\n\t\t\tthis.pendingTimer = null;\n\t\t}\n\t\tthis.pendingArgs = null;\n\t}\n\n\tprivate recalculateAndGlide(\n\t\tnodes: TNode[],\n\t\tedges: TEdge[],\n\t\tcurrentPositions: Map<string, XYPosition>\n\t): void {\n\t\tconst targetNodes = this.connectedOnly ? connectedOnly(nodes, edges) : nodes;\n\t\tif (targetNodes.length === 0) return;\n\n\t\t// Where nodes are right now: the caller's view, overridden by any glide still in\n\t\t// flight (the caller may not have applied the latest animated frame yet).\n\t\tconst startPositions = new Map<string, XYPosition>(currentPositions);\n\t\tif (this.activeTransitionCancel) {\n\t\t\tfor (const [id, pos] of this.livePositions) {\n\t\t\t\tstartPositions.set(id, pos);\n\t\t\t}\n\t\t\tthis.activeTransitionCancel();\n\t\t\tthis.activeTransitionCancel = null;\n\t\t}\n\n\t\t// Compute fresh layout targets. Only explicitly stored (pinned/manual) positions\n\t\t// are preserved; current positions are the glide *origin*, not the destination.\n\t\tconst layouted = ensureLayout(targetNodes, edges, this.layoutOptions);\n\n\t\tconst nextPositions = new Map<string, XYPosition>();\n\t\tconst transitions: NodeTransition<TNode>[] = [];\n\n\t\tfor (const node of layouted) {\n\t\t\tconst target = { ...node.position };\n\t\t\tnextPositions.set(node.id, target);\n\n\t\t\tconst current = startPositions.get(node.id);\n\t\t\tif (!current) continue; // brand-new node: appears directly at its target\n\n\t\t\tconst distance = Math.hypot(target.x - current.x, target.y - current.y);\n\t\t\tif (distance > 2) {\n\t\t\t\ttransitions.push({\n\t\t\t\t\tnode: { ...node, position: { ...current } },\n\t\t\t\t\tfrom: current,\n\t\t\t\t\tto: target,\n\t\t\t\t\ttrajectory: linearPositionTrajectory,\n\t\t\t\t\tduration: this.glideDurationMs\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tthis.targetPositions = nextPositions;\n\n\t\tif (transitions.length === 0) {\n\t\t\tthis.livePositions = new Map(nextPositions);\n\t\t\tthis.onLayoutUpdated?.(nextPositions);\n\t\t\treturn;\n\t\t}\n\n\t\tconst live = new Map<string, XYPosition>(nextPositions);\n\t\tfor (const trans of transitions) {\n\t\t\tlive.set(trans.node.id, { ...trans.from });\n\t\t}\n\t\tthis.livePositions = live;\n\n\t\tlet finished = false;\n\t\tconst cancel = runMultiNodeTransition(transitions, {\n\t\t\tclock: this.clock,\n\t\t\tonUpdate: (updated) => {\n\t\t\t\tconst frame = new Map<string, XYPosition>(this.livePositions);\n\t\t\t\tfor (const node of updated) {\n\t\t\t\t\tframe.set(node.id, { ...node.position });\n\t\t\t\t}\n\t\t\t\tthis.livePositions = frame;\n\t\t\t\tthis.onLayoutUpdated?.(frame);\n\t\t\t},\n\t\t\tonComplete: () => {\n\t\t\t\tfinished = true;\n\t\t\t\tthis.activeTransitionCancel = null;\n\t\t\t\tthis.livePositions = new Map(nextPositions);\n\t\t\t\tthis.onLayoutUpdated?.(nextPositions);\n\t\t\t}\n\t\t});\n\t\t// A zero-duration glide completes synchronously; don't keep a stale canceller.\n\t\tthis.activeTransitionCancel = finished ? null : cancel;\n\t}\n\n\tpublic destroy(): void {\n\t\tthis.clearPending();\n\t\tif (this.activeTransitionCancel) {\n\t\t\tthis.activeTransitionCancel();\n\t\t\tthis.activeTransitionCancel = null;\n\t\t}\n\t}\n}\n","import { boundsOf, estimateNodeSize } from './layout.js';\nimport type { CanvasNode, CanvasNodeStyle, Rect, XYPosition } from './types.js';\n\n/**\n * Edge (or centre line) used by `alignNodes`.\n * `center` aligns horizontal centres (a vertical line); `middle` aligns vertical centres.\n */\nexport type AlignEdge = 'left' | 'center' | 'right' | 'top' | 'middle' | 'bottom';\n\n/** Axis used by `distributeNodes`. `both` distributes horizontally and vertically. */\nexport type DistributeAxis = 'horizontal' | 'vertical' | 'both';\n\n/**\n * `gaps` leaves equal space between neighbouring nodes, `centres` spaces their centres equally.\n */\nexport type DistributeMode = 'gaps' | 'centres';\n\n/** Dimension(s) copied by `matchSize`. */\nexport type SizeDimension = 'width' | 'height' | 'both';\n\n/**\n * Partial node change produced by the arrange helpers. `position` is in the node's own\n * coordinate space (relative to its `parentId` parent when it has one).\n */\nexport interface CanvasNodeUpdate {\n\tid: string;\n\tposition?: XYPosition;\n\twidth?: number;\n\theight?: number;\n\tzIndex?: number;\n}\n\nexport interface NodeRectOptions {\n\t/** Width used when a node has no measured, explicit or style size and estimation is off. */\n\tdefaultWidth?: number;\n\t/** Height used when a node has no measured, explicit or style size and estimation is off. */\n\tdefaultHeight?: number;\n\t/** Estimate unmeasured node sizes from their label via `estimateNodeSize` (default `true`). */\n\testimate?: boolean;\n}\n\n/** Data key marking a node as locked. See `lockNodes`. */\nexport const LOCKED_DATA_KEY = 'locked';\n/** Data key holding the interaction flags a node had before it was locked. */\nexport const LOCKED_PREVIOUS_FLAGS_KEY = 'lockedPrevious';\n\nfunction parsePx(value: unknown): number | undefined {\n\tif (typeof value === 'number' && Number.isFinite(value)) return value;\n\tif (typeof value === 'string') {\n\t\tconst match = /^\\s*(-?\\d+(?:\\.\\d+)?)(px)?\\s*$/i.exec(value);\n\t\tif (match) return Number(match[1]);\n\t}\n\treturn undefined;\n}\n\n/**\n * Reads a pixel `width` / `height` from a node style, which may be a CSS string (SvelteFlow)\n * or a style object (React Flow). Non-pixel values (`%`, `auto`, ...) are ignored.\n */\nexport function readStyleSize(style: CanvasNodeStyle | undefined | null): {\n\twidth?: number;\n\theight?: number;\n} {\n\tif (!style) return {};\n\tif (typeof style === 'object') {\n\t\treturn { width: parsePx(style.width), height: parsePx(style.height) };\n\t}\n\tconst read = (prop: string) => {\n\t\tconst match = new RegExp(`(?:^|;)\\\\s*${prop}\\\\s*:\\\\s*([^;]+)`, 'i').exec(style);\n\t\treturn match ? parsePx(match[1]) : undefined;\n\t};\n\treturn { width: read('width'), height: read('height') };\n}\n\n/**\n * Returns `style` with its pixel `width` / `height` replaced, keeping its representation\n * (string or object). Only dimensions already present in the style are rewritten, because\n * xyflow applies the node's own `width` / `height` props otherwise.\n */\nexport function withStyleSize(\n\tstyle: CanvasNodeStyle | undefined | null,\n\tsize: { width?: number; height?: number }\n): CanvasNodeStyle | undefined {\n\tif (!style) return style ?? undefined;\n\tif (typeof style === 'object') {\n\t\tconst next: Record<string, any> = { ...style };\n\t\tif (size.width !== undefined && 'width' in next) next.width = size.width;\n\t\tif (size.height !== undefined && 'height' in next) next.height = size.height;\n\t\treturn next;\n\t}\n\tlet next = style;\n\tfor (const prop of ['width', 'height'] as const) {\n\t\tconst value = size[prop];\n\t\tif (value === undefined) continue;\n\t\tnext = next.replace(\n\t\t\tnew RegExp(`((?:^|;)\\\\s*${prop}\\\\s*:\\\\s*)[^;]+`, 'i'),\n\t\t\t`$1${value}px`\n\t\t);\n\t}\n\treturn next;\n}\n\n/**\n * Resolves a node's size: measured DOM size, then explicit `width` / `height`, then a pixel\n * size in `style`, then a label-based estimate (or the defaults when `estimate` is `false`).\n */\nexport function getNodeSize(\n\tnode: CanvasNode,\n\toptions: NodeRectOptions = {}\n): { width: number; height: number } {\n\tconst styleSize = readStyleSize(node.style);\n\tlet width = node.measured?.width ?? node.width ?? styleSize.width;\n\tlet height = node.measured?.height ?? node.height ?? styleSize.height;\n\tif (width === undefined || height === undefined) {\n\t\tconst fallback =\n\t\t\toptions.estimate === false\n\t\t\t\t? { width: options.defaultWidth ?? 200, height: options.defaultHeight ?? 60 }\n\t\t\t\t: estimateNodeSize(node, options.defaultWidth, options.defaultHeight);\n\t\twidth ??= fallback.width;\n\t\theight ??= fallback.height;\n\t}\n\treturn { width, height };\n}\n\n/**\n * Absolute (flow-space) position of a node, following its `parentId` chain.\n * Cycles and missing parents are tolerated.\n */\nexport function getAbsolutePosition(\n\tnode: CanvasNode,\n\tlookup: ReadonlyMap<string, CanvasNode>\n): XYPosition {\n\tlet x = node.position.x;\n\tlet y = node.position.y;\n\tconst seen = new Set<string>([node.id]);\n\tlet parentId = node.parentId;\n\twhile (parentId && !seen.has(parentId)) {\n\t\tconst parent = lookup.get(parentId);\n\t\tif (!parent) break;\n\t\tseen.add(parentId);\n\t\tx += parent.position.x;\n\t\ty += parent.position.y;\n\t\tparentId = parent.parentId;\n\t}\n\treturn { x, y };\n}\n\n/** Builds an id lookup for a node array. */\nexport function createNodeLookup<TNode extends CanvasNode>(nodes: TNode[]): Map<string, TNode> {\n\treturn new Map(nodes.map((n) => [n.id, n]));\n}\n\n/** Absolute flow-space rectangle of a node. */\nexport function getNodeRect(\n\tnode: CanvasNode,\n\tlookup?: ReadonlyMap<string, CanvasNode>,\n\toptions?: NodeRectOptions\n): Rect {\n\tconst { x, y } = node.parentId && lookup ? getAbsolutePosition(node, lookup) : node.position;\n\treturn { x, y, ...getNodeSize(node, options) };\n}\n\n/**\n * Whether a node has been locked with `lockNodes`.\n */\nexport function isLocked(node: CanvasNode | undefined | null): boolean {\n\treturn node?.data?.[LOCKED_DATA_KEY] === true;\n}\n\n/**\n * Locks nodes: marks `data.locked = true` and sets `draggable: false` and `deletable: false`\n * so both SvelteFlow and React Flow stop dragging and deleting them. Nodes stay `selectable`\n * so they can be unlocked again. The previous flags are kept in `data.lockedPrevious` and\n * restored by `unlockNodes`. Returns a new array; untouched nodes keep their identity.\n */\nexport function lockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[] {\n\tconst idSet = new Set(ids);\n\treturn nodes.map((node) => {\n\t\tif (!idSet.has(node.id) || isLocked(node)) return node;\n\t\tconst previous: Record<string, boolean> = {};\n\t\tif (node.draggable !== undefined) previous.draggable = node.draggable;\n\t\tif (node.deletable !== undefined) previous.deletable = node.deletable;\n\t\treturn {\n\t\t\t...node,\n\t\t\tdraggable: false,\n\t\t\tdeletable: false,\n\t\t\tdata: { ...node.data, [LOCKED_DATA_KEY]: true, [LOCKED_PREVIOUS_FLAGS_KEY]: previous }\n\t\t};\n\t});\n}\n\n/**\n * Reverses `lockNodes`, restoring the `draggable` / `deletable` flags the node had before.\n */\nexport function unlockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[] {\n\tconst idSet = new Set(ids);\n\treturn nodes.map((node) => {\n\t\tif (!idSet.has(node.id) || !isLocked(node)) return node;\n\t\tconst {\n\t\t\t[LOCKED_DATA_KEY]: _locked,\n\t\t\t[LOCKED_PREVIOUS_FLAGS_KEY]: previous = {},\n\t\t\t...data\n\t\t} = node.data as Record<string, any>;\n\t\tconst next = { ...node, data } as TNode;\n\t\tdelete next.draggable;\n\t\tdelete next.deletable;\n\t\tif (previous.draggable !== undefined) next.draggable = previous.draggable;\n\t\tif (previous.deletable !== undefined) next.deletable = previous.deletable;\n\t\treturn next;\n\t});\n}\n\n/** Locks the given nodes if any of them is unlocked, otherwise unlocks them all. */\nexport function toggleLockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[] {\n\tconst idSet = new Set(ids);\n\tconst anyUnlocked = nodes.some((n) => idSet.has(n.id) && !isLocked(n));\n\treturn anyUnlocked ? lockNodes(nodes, idSet) : unlockNodes(nodes, idSet);\n}\n\ninterface Item {\n\tnode: CanvasNode;\n\trect: Rect;\n\tlocked: boolean;\n}\n\nfunction collect(nodes: CanvasNode[], ids: Iterable<string>, options?: NodeRectOptions) {\n\tconst lookup = createNodeLookup(nodes);\n\tconst items: Item[] = [];\n\tconst seen = new Set<string>();\n\tfor (const id of ids) {\n\t\tconst node = lookup.get(id);\n\t\tif (!node || seen.has(id)) continue;\n\t\tseen.add(id);\n\t\titems.push({ node, rect: getNodeRect(node, lookup, options), locked: isLocked(node) });\n\t}\n\treturn { lookup, items };\n}\n\n/**\n * Converts absolute target positions into node-relative `position` updates. When a parent\n * moves in the same operation, children are made relative to the parent's new position.\n */\nfunction toPositionUpdates(\n\tlookup: Map<string, CanvasNode>,\n\ttargets: Map<string, XYPosition>\n): CanvasNodeUpdate[] {\n\tconst updates: CanvasNodeUpdate[] = [];\n\tfor (const [id, abs] of targets) {\n\t\tconst node = lookup.get(id)!;\n\t\tlet position = abs;\n\t\tif (node.parentId && lookup.has(node.parentId)) {\n\t\t\tconst parent = lookup.get(node.parentId)!;\n\t\t\tconst parentAbs = targets.get(parent.id) ?? getAbsolutePosition(parent, lookup);\n\t\t\tposition = { x: abs.x - parentAbs.x, y: abs.y - parentAbs.y };\n\t\t}\n\t\tif (position.x !== node.position.x || position.y !== node.position.y) {\n\t\t\tupdates.push({ id, position });\n\t\t}\n\t}\n\treturn updates;\n}\n\nexport interface AlignNodesOptions extends NodeRectOptions {\n\t/**\n\t * What to align to: the selection bounding box (default) or a fixed rectangle.\n\t * Locked nodes never move but still count towards the selection bounds, so they act as anchors.\n\t */\n\tto?: 'selection' | Rect;\n}\n\n/**\n * Aligns nodes to a shared edge or centre line of their bounding box (Freeform's\n * Align Left / Center / Right / Top / Middle / Bottom). Needs at least two nodes unless\n * `options.to` is a rectangle. Locked nodes are not moved.\n */\nexport function alignNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\tedge: AlignEdge,\n\toptions: AlignNodesOptions = {}\n): CanvasNodeUpdate[] {\n\tconst { lookup, items } = collect(nodes, ids, options);\n\tconst fixed = typeof options.to === 'object' ? options.to : null;\n\tif (items.length === 0 || (!fixed && items.length < 2)) return [];\n\tconst bounds = fixed ?? boundsOf(items.map((i) => i.rect))!;\n\n\tconst targets = new Map<string, XYPosition>();\n\tfor (const { node, rect, locked } of items) {\n\t\tif (locked) continue;\n\t\tlet { x, y } = rect;\n\t\tswitch (edge) {\n\t\t\tcase 'left':\n\t\t\t\tx = bounds.x;\n\t\t\t\tbreak;\n\t\t\tcase 'center':\n\t\t\t\tx = bounds.x + bounds.width / 2 - rect.width / 2;\n\t\t\t\tbreak;\n\t\t\tcase 'right':\n\t\t\t\tx = bounds.x + bounds.width - rect.width;\n\t\t\t\tbreak;\n\t\t\tcase 'top':\n\t\t\t\ty = bounds.y;\n\t\t\t\tbreak;\n\t\t\tcase 'middle':\n\t\t\t\ty = bounds.y + bounds.height / 2 - rect.height / 2;\n\t\t\t\tbreak;\n\t\t\tcase 'bottom':\n\t\t\t\ty = bounds.y + bounds.height - rect.height;\n\t\t\t\tbreak;\n\t\t}\n\t\ttargets.set(node.id, { x, y });\n\t}\n\treturn toPositionUpdates(lookup, targets);\n}\n\nexport interface DistributeNodesOptions extends NodeRectOptions {\n\t/** Explicit gap between neighbours for `gaps` mode. Default: spread evenly between the outer nodes. */\n\tgap?: number;\n}\n\nfunction distributeAxis(\n\titems: Item[],\n\thorizontal: boolean,\n\tmode: DistributeMode,\n\tgapOverride: number | undefined,\n\ttargets: Map<string, XYPosition>\n) {\n\tconst start = (r: Rect) => (horizontal ? r.x : r.y);\n\tconst size = (r: Rect) => (horizontal ? r.width : r.height);\n\tconst sorted = [...items].sort(\n\t\t(a, b) => start(a.rect) + size(a.rect) / 2 - (start(b.rect) + size(b.rect) / 2)\n\t);\n\tconst first = sorted[0].rect;\n\tconst last = sorted[sorted.length - 1].rect;\n\tconst n = sorted.length;\n\tconst set = (item: Item, value: number) => {\n\t\tif (item.locked) return;\n\t\tconst current = targets.get(item.node.id) ?? { x: item.rect.x, y: item.rect.y };\n\t\ttargets.set(item.node.id, horizontal ? { ...current, x: value } : { ...current, y: value });\n\t};\n\n\tif (mode === 'centres') {\n\t\tconst c0 = start(first) + size(first) / 2;\n\t\tconst c1 = start(last) + size(last) / 2;\n\t\tconst step = (c1 - c0) / (n - 1);\n\t\tsorted.forEach((item, i) => set(item, c0 + step * i - size(item.rect) / 2));\n\t\treturn;\n\t}\n\n\tconst span = start(last) + size(last) - start(first);\n\tconst total = sorted.reduce((sum, item) => sum + size(item.rect), 0);\n\tconst gap = gapOverride ?? (span - total) / (n - 1);\n\tlet cursor = start(first);\n\tfor (const item of sorted) {\n\t\tset(item, cursor);\n\t\tcursor += size(item.rect) + gap;\n\t}\n}\n\n/**\n * Distributes nodes along an axis (Freeform's Distribute Horizontally / Vertically / Evenly).\n * The outermost nodes stay put and the others are spaced with equal gaps (`gaps`, default)\n * or equally spaced centres (`centres`). Needs at least three nodes (two with an explicit\n * `gap`). Locked nodes keep their place in the order but are not moved.\n */\nexport function distributeNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\taxis: DistributeAxis,\n\tmode: DistributeMode = 'gaps',\n\toptions: DistributeNodesOptions = {}\n): CanvasNodeUpdate[] {\n\tconst { lookup, items } = collect(nodes, ids, options);\n\tconst minimum = options.gap !== undefined && mode === 'gaps' ? 2 : 3;\n\tif (items.length < minimum) return [];\n\tconst targets = new Map<string, XYPosition>();\n\tif (axis !== 'vertical') distributeAxis(items, true, mode, options.gap, targets);\n\tif (axis !== 'horizontal') distributeAxis(items, false, mode, options.gap, targets);\n\treturn toPositionUpdates(lookup, targets);\n}\n\nexport interface MatchSizeOptions extends NodeRectOptions {\n\t/**\n\t * Size source: the `largest` (default) or `smallest` node, the `first` id given,\n\t * or a specific node id.\n\t */\n\treference?: 'largest' | 'smallest' | 'first' | { id: string };\n\t/** Keep each node's centre in place instead of its top-left corner (default `false`). */\n\tkeepCentre?: boolean;\n}\n\n/**\n * Makes nodes the same width, height or both (Freeform's Make Same Size). Returns size\n * updates (plus position updates with `keepCentre`). Locked nodes are not resized but can\n * be the reference.\n */\nexport function matchSize(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\tdimension: SizeDimension,\n\toptions: MatchSizeOptions = {}\n): CanvasNodeUpdate[] {\n\tconst { items } = collect(nodes, ids, options);\n\tif (items.length < 2) return [];\n\tconst reference = options.reference ?? 'largest';\n\n\tconst pick = (dim: 'width' | 'height'): number => {\n\t\tif (typeof reference === 'object') {\n\t\t\tconst item = items.find((i) => i.node.id === reference.id);\n\t\t\tif (item) return item.rect[dim];\n\t\t}\n\t\tif (reference === 'first') return items[0].rect[dim];\n\t\tconst values = items.map((i) => i.rect[dim]);\n\t\treturn reference === 'smallest' ? Math.min(...values) : Math.max(...values);\n\t};\n\tconst width = dimension !== 'height' ? pick('width') : undefined;\n\tconst height = dimension !== 'width' ? pick('height') : undefined;\n\n\tconst updates: CanvasNodeUpdate[] = [];\n\tfor (const { node, rect, locked } of items) {\n\t\tif (locked) continue;\n\t\tconst update: CanvasNodeUpdate = { id: node.id };\n\t\tif (width !== undefined && width !== rect.width) update.width = width;\n\t\tif (height !== undefined && height !== rect.height) update.height = height;\n\t\tif (update.width === undefined && update.height === undefined) continue;\n\t\tif (options.keepCentre) {\n\t\t\tupdate.position = {\n\t\t\t\tx: node.position.x - ((update.width ?? rect.width) - rect.width) / 2,\n\t\t\t\ty: node.position.y - ((update.height ?? rect.height) - rect.height) / 2\n\t\t\t};\n\t\t}\n\t\tupdates.push(update);\n\t}\n\treturn updates;\n}\n\n/** Z-order operation. */\nexport type ZOrderOperation = 'bringForward' | 'sendBackward' | 'bringToFront' | 'sendToBack';\n\n/**\n * Restacks nodes (Bring Forward / Send Backward / Bring to Front / Send to Back).\n *\n * Both SvelteFlow and React Flow stack nodes by `zIndex` (missing = 0) and, for equal\n * `zIndex`, by array order. This computes that effective order for the siblings that share\n * the targets' parent, applies the operation, and returns `zIndex` updates assigning each\n * sibling its new rank. The array order is left alone, because xyflow requires parents to\n * precede their children. Locked nodes are not restacked. \"Forward\" moves past the next\n * sibling the node overlaps, falling back to the next sibling when nothing overlaps.\n */\nexport function reorderNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\toperation: ZOrderOperation,\n\toptions: NodeRectOptions = {}\n): CanvasNodeUpdate[] {\n\tconst lookup = createNodeLookup(nodes);\n\tconst targetIds = new Set(\n\t\t[...ids].filter((id) => lookup.has(id) && !isLocked(lookup.get(id)))\n\t);\n\tif (targetIds.size === 0) return [];\n\n\t// Restack each sibling group (nodes sharing a parent) independently.\n\tconst groups = new Map<string, CanvasNode[]>();\n\tfor (const node of nodes) {\n\t\tconst key = node.parentId ?? '';\n\t\tif (!groups.has(key)) groups.set(key, []);\n\t\tgroups.get(key)!.push(node);\n\t}\n\n\tconst updates: CanvasNodeUpdate[] = [];\n\tfor (const siblings of groups.values()) {\n\t\tif (!siblings.some((n) => targetIds.has(n.id))) continue;\n\t\tconst order = siblings\n\t\t\t.map((node, index) => ({ node, index, z: typeof node.zIndex === 'number' ? node.zIndex : 0 }))\n\t\t\t.sort((a, b) => a.z - b.z || a.index - b.index)\n\t\t\t.map((entry) => entry.node);\n\t\tconst isTarget = (n: CanvasNode) => targetIds.has(n.id);\n\t\tlet next: CanvasNode[];\n\n\t\tif (operation === 'bringToFront') {\n\t\t\tnext = [...order.filter((n) => !isTarget(n)), ...order.filter(isTarget)];\n\t\t} else if (operation === 'sendToBack') {\n\t\t\tnext = [...order.filter(isTarget), ...order.filter((n) => !isTarget(n))];\n\t\t} else {\n\t\t\tnext = [...order];\n\t\t\tconst forward = operation === 'bringForward';\n\t\t\tconst rects = new Map(next.map((n) => [n.id, getNodeRect(n, lookup, options)]));\n\t\t\tconst intersects = (a: Rect, b: Rect) =>\n\t\t\t\ta.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;\n\t\t\t// Walk from the end we are moving towards so adjacent targets keep their relative order.\n\t\t\tconst indices = next.map((_, i) => i);\n\t\t\tif (forward) indices.reverse();\n\t\t\tfor (const i of indices) {\n\t\t\t\tconst node = next[i];\n\t\t\t\tif (!isTarget(node)) continue;\n\t\t\t\tconst step = forward ? 1 : -1;\n\t\t\t\tlet j = i + step;\n\t\t\t\tlet swapWith = -1;\n\t\t\t\tlet firstNonTarget = -1;\n\t\t\t\twhile (j >= 0 && j < next.length) {\n\t\t\t\t\tif (!isTarget(next[j])) {\n\t\t\t\t\t\tif (firstNonTarget === -1) firstNonTarget = j;\n\t\t\t\t\t\tif (intersects(rects.get(node.id)!, rects.get(next[j].id)!)) {\n\t\t\t\t\t\t\tswapWith = j;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tj += step;\n\t\t\t\t}\n\t\t\t\tif (swapWith === -1) swapWith = firstNonTarget;\n\t\t\t\tif (swapWith === -1) continue;\n\t\t\t\tnext.splice(i, 1);\n\t\t\t\tnext.splice(swapWith, 0, node);\n\t\t\t}\n\t\t}\n\n\t\tif (next.every((node, i) => node === order[i])) continue;\n\t\tnext.forEach((node, rank) => {\n\t\t\tconst current = typeof node.zIndex === 'number' ? node.zIndex : 0;\n\t\t\tif (current !== rank) updates.push({ id: node.id, zIndex: rank });\n\t\t});\n\t}\n\treturn updates;\n}\n\n/**\n * Applies `CanvasNodeUpdate`s to a node array, returning a new array. Unchanged nodes keep\n * their identity. Size updates set `width` / `height` and rewrite pixel sizes already present\n * in `style` (string or object). An existing `measured` size is updated too, so geometry\n * helpers see the new size before xyflow re-measures the node.\n */\nexport function applyNodeUpdates<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tupdates: CanvasNodeUpdate[]\n): TNode[] {\n\tif (updates.length === 0) return nodes;\n\tconst byId = new Map<string, CanvasNodeUpdate>();\n\tfor (const update of updates) {\n\t\tbyId.set(update.id, { ...byId.get(update.id), ...update });\n\t}\n\treturn nodes.map((node) => {\n\t\tconst update = byId.get(node.id);\n\t\tif (!update) return node;\n\t\tconst next = { ...node } as TNode;\n\t\tif (update.position) next.position = { ...update.position };\n\t\tif (update.zIndex !== undefined) (next as CanvasNode).zIndex = update.zIndex;\n\t\tif (update.width !== undefined || update.height !== undefined) {\n\t\t\tif (update.width !== undefined) next.width = update.width;\n\t\t\tif (update.height !== undefined) next.height = update.height;\n\t\t\tif (node.style) next.style = withStyleSize(node.style, update);\n\t\t\tif (node.measured) {\n\t\t\t\tnext.measured = {\n\t\t\t\t\t...node.measured,\n\t\t\t\t\t...(update.width !== undefined ? { width: update.width } : {}),\n\t\t\t\t\t...(update.height !== undefined ? { height: update.height } : {})\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\treturn next;\n\t});\n}\n\n/**\n * Inverse of a set of updates against the current nodes, for undo. Restores the previous\n * position / size / zIndex of every node an update touches.\n */\nexport function invertNodeUpdates(\n\tnodes: CanvasNode[],\n\tupdates: CanvasNodeUpdate[]\n): CanvasNodeUpdate[] {\n\tconst lookup = createNodeLookup(nodes);\n\tconst inverse: CanvasNodeUpdate[] = [];\n\tfor (const update of updates) {\n\t\tconst node = lookup.get(update.id);\n\t\tif (!node) continue;\n\t\tconst back: CanvasNodeUpdate = { id: node.id };\n\t\tif (update.position) back.position = { ...node.position };\n\t\tif (update.zIndex !== undefined) back.zIndex = typeof node.zIndex === 'number' ? node.zIndex : 0;\n\t\tif (update.width !== undefined || update.height !== undefined) {\n\t\t\tconst size = getNodeSize(node);\n\t\t\tif (update.width !== undefined) back.width = size.width;\n\t\t\tif (update.height !== undefined) back.height = size.height;\n\t\t}\n\t\tinverse.push(back);\n\t}\n\treturn inverse;\n}\n","import { boundsOf } from './layout.js';\nimport { createNodeLookup, getNodeRect, type CanvasNodeUpdate, type NodeRectOptions } from './arrange.js';\nimport type { CanvasNode, Rect, XYPosition } from './types.js';\n\n/**\n * A guide line to render while dragging, in flow coordinates.\n * `edge` and `centre` guides are alignment lines; `spacing` guides mark equal gaps.\n * `measure` lines (with a distance `label`) and dashed `extension` lines come from the\n * distance measuring helpers (`measureBetween`, `measureSelection`).\n */\nexport interface GuideLine {\n\torientation: 'vertical' | 'horizontal';\n\tkind: 'edge' | 'centre' | 'spacing' | 'measure' | 'extension';\n\tx1: number;\n\ty1: number;\n\tx2: number;\n\ty2: number;\n\t/** Text drawn at the middle of the line (measuring distances). */\n\tlabel?: string;\n}\n\n/**\n * Snaps `value` to a grid with the given `spacing` and `origin`. A spacing of 0 or less\n * (or a non-finite one) leaves the value unchanged.\n *\n * Ported from vectorcraft-main/crates/geom/src/snap.rs (MIT OR Apache-2.0, © ArtCraft Team\n * and contributors). See THIRD_PARTY_NOTICES.md.\n */\nexport function snapToGrid(value: number, spacing: number, origin = 0): number {\n\tif (!(spacing > 0) || !Number.isFinite(spacing)) return value;\n\treturn Math.round((value - origin) / spacing) * spacing + origin;\n}\n\nexport interface SnapGuidesOptions {\n\t/** Current viewport zoom; the threshold is in screen pixels and divided by it (default 1). */\n\tzoom?: number;\n\t/** Snap distance in screen pixels (default 6). */\n\tthreshold?: number;\n\t/** Snap edges to other shapes' edges (default `true`). */\n\tedges?: boolean;\n\t/** Snap centres to other shapes' centres (default `true`). */\n\tcentres?: boolean;\n\t/** Snap to equal spacing between neighbouring shapes in the same row / column (default `true`). */\n\tspacing?: boolean;\n\t/**\n\t * Grid spacing in flow units (default 0, off). An axis that does not snap to a shape snaps\n\t * its top-left (drag) or moving edge (resize) to the grid; grid snaps draw no guides.\n\t */\n\tgridSize?: number;\n\t/** Grid origin in flow units (default `{ x: 0, y: 0 }`). */\n\tgridOrigin?: XYPosition;\n}\n\nexport interface SnapResult {\n\t/** Snapped top-left of the moving rectangle. */\n\tposition: XYPosition;\n\t/** Offset applied to the input rectangle (`position - rect`). */\n\toffset: XYPosition;\n\t/** Guide segments to draw for the snap that was applied. */\n\tguides: GuideLine[];\n\t/** Whether x snapped to a shape (grid snaps do not count). */\n\tsnappedX: boolean;\n\tsnappedY: boolean;\n}\n\ninterface AxisEntries {\n\tvalues: Float64Array;\n\trects: Rect[];\n}\n\nfunction buildAxis(entries: { value: number; rect: Rect }[]): AxisEntries {\n\tentries.sort((a, b) => a.value - b.value);\n\treturn {\n\t\tvalues: Float64Array.from(entries.map((e) => e.value)),\n\t\trects: entries.map((e) => e.rect)\n\t};\n}\n\n/** First index whose value is >= `v`. */\nfunction lowerBound(values: Float64Array, v: number): number {\n\tlet lo = 0;\n\tlet hi = values.length;\n\twhile (lo < hi) {\n\t\tconst mid = (lo + hi) >>> 1;\n\t\tif (values[mid] < v) lo = mid + 1;\n\t\telse hi = mid;\n\t}\n\treturn lo;\n}\n\n/**\n * Pre-sorted snap targets for a set of static rectangles. Build it once when a drag starts\n * and reuse it on every pointer move; each query is a binary search, so snapping stays\n * cheap with hundreds or thousands of shapes.\n */\nexport interface GuideIndex {\n\trects: Rect[];\n\tedgeX: AxisEntries;\n\tcentreX: AxisEntries;\n\tedgeY: AxisEntries;\n\tcentreY: AxisEntries;\n}\n\n/** Builds a `GuideIndex` from the rectangles the moving shape may snap to. */\nexport function createGuideIndex(rects: Rect[]): GuideIndex {\n\tconst edgeX: { value: number; rect: Rect }[] = [];\n\tconst centreX: { value: number; rect: Rect }[] = [];\n\tconst edgeY: { value: number; rect: Rect }[] = [];\n\tconst centreY: { value: number; rect: Rect }[] = [];\n\tfor (const rect of rects) {\n\t\tedgeX.push({ value: rect.x, rect }, { value: rect.x + rect.width, rect });\n\t\tcentreX.push({ value: rect.x + rect.width / 2, rect });\n\t\tedgeY.push({ value: rect.y, rect }, { value: rect.y + rect.height, rect });\n\t\tcentreY.push({ value: rect.y + rect.height / 2, rect });\n\t}\n\treturn {\n\t\trects,\n\t\tedgeX: buildAxis(edgeX),\n\t\tcentreX: buildAxis(centreX),\n\t\tedgeY: buildAxis(edgeY),\n\t\tcentreY: buildAxis(centreY)\n\t};\n}\n\n/** Nearest indexed value within `tolerance` of `v`, as a signed delta, or `null`. */\nfunction nearest(axis: AxisEntries, v: number, tolerance: number): number | null {\n\tconst { values } = axis;\n\tlet best: number | null = null;\n\tfor (let i = lowerBound(values, v - tolerance); i < values.length && values[i] <= v + tolerance; i++) {\n\t\tconst delta = values[i] - v;\n\t\tif (best === null || Math.abs(delta) < Math.abs(best)) best = delta;\n\t}\n\treturn best;\n}\n\n/** Rectangles whose indexed value equals `v` (within `eps`). */\nfunction matches(axis: AxisEntries, v: number, eps: number): Rect[] {\n\tconst { values, rects } = axis;\n\tconst out: Rect[] = [];\n\tfor (let i = lowerBound(values, v - eps); i < values.length && values[i] <= v + eps; i++) {\n\t\tout.push(rects[i]);\n\t}\n\treturn out;\n}\n\ninterface SpacingCandidate {\n\tdelta: number;\n\tguides: GuideLine[];\n}\n\n/**\n * Equal-spacing candidates along one axis. Looks at shapes in the same row (horizontal) or\n * column (vertical) as the moving rectangle and proposes: centring between the nearest\n * neighbours, or repeating the gap a neighbour has to its own neighbour.\n */\nfunction spacingCandidate(\n\trects: Rect[],\n\tmoving: Rect,\n\thorizontal: boolean,\n\ttolerance: number\n): SpacingCandidate | null {\n\tconst start = (r: Rect) => (horizontal ? r.x : r.y);\n\tconst end = (r: Rect) => (horizontal ? r.x + r.width : r.y + r.height);\n\tconst crossStart = (r: Rect) => (horizontal ? r.y : r.x);\n\tconst crossEnd = (r: Rect) => (horizontal ? r.y + r.height : r.x + r.width);\n\tconst size = end(moving) - start(moving);\n\tconst inLane = (r: Rect, lane: Rect) => crossStart(r) < crossEnd(lane) && crossEnd(r) > crossStart(lane);\n\n\tconst lane = rects.filter((r) => inLane(r, moving));\n\tif (lane.length === 0) return null;\n\n\tlet before: Rect | null = null;\n\tlet after: Rect | null = null;\n\tfor (const r of lane) {\n\t\tif (end(r) <= start(moving) + tolerance && (!before || end(r) > end(before))) before = r;\n\t\tif (start(r) >= end(moving) - tolerance && (!after || start(r) < start(after))) after = r;\n\t}\n\n\tconst neighbourOf = (r: Rect, forward: boolean): Rect | null => {\n\t\tlet best: Rect | null = null;\n\t\tfor (const other of lane) {\n\t\t\tif (other === r || !inLane(other, r)) continue;\n\t\t\tif (forward ? start(other) >= end(r) : end(other) <= start(r)) {\n\t\t\t\tif (!best || (forward ? start(other) < start(best) : end(other) > end(best))) best = other;\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t};\n\n\tconst crossMid = (a: Rect, b: Rect) =>\n\t\t(Math.max(crossStart(a), crossStart(b)) + Math.min(crossEnd(a), crossEnd(b))) / 2;\n\tconst segment = (from: number, to: number, cross: number): GuideLine =>\n\t\thorizontal\n\t\t\t? { orientation: 'horizontal', kind: 'spacing', x1: from, y1: cross, x2: to, y2: cross }\n\t\t\t: { orientation: 'vertical', kind: 'spacing', x1: cross, y1: from, x2: cross, y2: to };\n\n\tconst candidates: { pos: number; guides: (pos: number) => GuideLine[] }[] = [];\n\tif (before && after) {\n\t\tconst b = before;\n\t\tconst a = after;\n\t\tconst pos = (end(b) + start(a) - size) / 2;\n\t\tif (pos >= end(b)) {\n\t\t\tcandidates.push({\n\t\t\t\tpos,\n\t\t\t\tguides: (p) => [\n\t\t\t\t\tsegment(end(b), p, crossMid(b, moving)),\n\t\t\t\t\tsegment(p + size, start(a), crossMid(a, moving))\n\t\t\t\t]\n\t\t\t});\n\t\t}\n\t}\n\tif (before) {\n\t\tconst b = before;\n\t\tconst prev = neighbourOf(b, false);\n\t\tif (prev) {\n\t\t\tconst gap = start(b) - end(prev);\n\t\t\tcandidates.push({\n\t\t\t\tpos: end(b) + gap,\n\t\t\t\tguides: (p) => [segment(end(prev), start(b), crossMid(prev, b)), segment(end(b), p, crossMid(b, moving))]\n\t\t\t});\n\t\t}\n\t}\n\tif (after) {\n\t\tconst a = after;\n\t\tconst next = neighbourOf(a, true);\n\t\tif (next) {\n\t\t\tconst gap = start(next) - end(a);\n\t\t\tcandidates.push({\n\t\t\t\tpos: start(a) - gap - size,\n\t\t\t\tguides: (p) => [segment(p + size, start(a), crossMid(a, moving)), segment(end(a), start(next), crossMid(a, next))]\n\t\t\t});\n\t\t}\n\t}\n\n\tlet best: SpacingCandidate | null = null;\n\tfor (const c of candidates) {\n\t\tconst delta = c.pos - start(moving);\n\t\tif (Math.abs(delta) <= tolerance && (!best || Math.abs(delta) < Math.abs(best.delta))) {\n\t\t\tbest = { delta, guides: c.guides(c.pos) };\n\t\t}\n\t}\n\treturn best;\n}\n\n/**\n * Computes smart-guide snapping for a moving rectangle (a dragged node or a selection's\n * bounding box) against other shapes. Edges snap to edges and centres to centres within\n * `threshold` screen pixels (converted to flow units via `zoom`). Each axis snaps\n * independently to its nearest candidate. Returns the snapped position and the guides to draw.\n */\nexport function computeSnapGuides(\n\tmoving: Rect,\n\ttargets: Rect[] | GuideIndex,\n\toptions: SnapGuidesOptions = {}\n): SnapResult {\n\tconst index = Array.isArray(targets) ? createGuideIndex(targets) : targets;\n\tconst zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;\n\tconst tolerance = (options.threshold ?? 6) / zoom;\n\tconst useEdges = options.edges !== false;\n\tconst useCentres = options.centres !== false;\n\tconst useSpacing = options.spacing !== false;\n\tconst eps = 0.5 / zoom;\n\n\tconst snapAxis = (horizontal: boolean) => {\n\t\tconst start = horizontal ? moving.x : moving.y;\n\t\tconst size = horizontal ? moving.width : moving.height;\n\t\tconst edgeAxis = horizontal ? index.edgeX : index.edgeY;\n\t\tconst centreAxis = horizontal ? index.centreX : index.centreY;\n\t\tlet delta: number | null = null;\n\t\tconst consider = (d: number | null) => {\n\t\t\tif (d !== null && (delta === null || Math.abs(d) < Math.abs(delta))) delta = d;\n\t\t};\n\t\tif (useEdges) {\n\t\t\tconsider(nearest(edgeAxis, start, tolerance));\n\t\t\tconsider(nearest(edgeAxis, start + size, tolerance));\n\t\t}\n\t\tif (useCentres) consider(nearest(centreAxis, start + size / 2, tolerance));\n\t\tconst spacing = useSpacing ? spacingCandidate(index.rects, moving, horizontal, tolerance) : null;\n\t\tif (spacing && (delta === null || Math.abs(spacing.delta) < Math.abs(delta) - eps)) {\n\t\t\treturn { delta: spacing.delta, spacing };\n\t\t}\n\t\treturn { delta: delta as number | null, spacing: null };\n\t};\n\n\tconst sx = snapAxis(true);\n\tconst sy = snapAxis(false);\n\tconst grid = options.gridSize ?? 0;\n\tconst gridDelta = (v: number, origin: number) => snapToGrid(v, grid, origin) - v;\n\tconst dx = sx.delta ?? (grid > 0 ? gridDelta(moving.x, options.gridOrigin?.x ?? 0) : 0);\n\tconst dy = sy.delta ?? (grid > 0 ? gridDelta(moving.y, options.gridOrigin?.y ?? 0) : 0);\n\tconst snapped: Rect = { x: moving.x + dx, y: moving.y + dy, width: moving.width, height: moving.height };\n\tconst guides: GuideLine[] = [];\n\n\tconst alignmentGuides = (horizontal: boolean) => {\n\t\tconst start = horizontal ? snapped.x : snapped.y;\n\t\tconst size = horizontal ? snapped.width : snapped.height;\n\t\tconst lines: { value: number; kind: 'edge' | 'centre'; axis: AxisEntries }[] = [];\n\t\tif (useEdges) {\n\t\t\tconst axis = horizontal ? index.edgeX : index.edgeY;\n\t\t\tlines.push({ value: start, kind: 'edge', axis }, { value: start + size, kind: 'edge', axis });\n\t\t}\n\t\tif (useCentres) {\n\t\t\tlines.push({ value: start + size / 2, kind: 'centre', axis: horizontal ? index.centreX : index.centreY });\n\t\t}\n\t\tfor (const line of lines) {\n\t\t\tconst aligned = matches(line.axis, line.value, eps);\n\t\t\tif (aligned.length === 0) continue;\n\t\t\tconst span = boundsOf([snapped, ...aligned])!;\n\t\t\tguides.push(\n\t\t\t\thorizontal\n\t\t\t\t\t? { orientation: 'vertical', kind: line.kind, x1: line.value, y1: span.y, x2: line.value, y2: span.y + span.height }\n\t\t\t\t\t: { orientation: 'horizontal', kind: line.kind, x1: span.x, y1: line.value, x2: span.x + span.width, y2: line.value }\n\t\t\t);\n\t\t}\n\t};\n\n\tif (sx.delta !== null) {\n\t\tif (sx.spacing) guides.push(...shiftGuides(sx.spacing.guides, 0, dy));\n\t\telse alignmentGuides(true);\n\t}\n\tif (sy.delta !== null) {\n\t\tif (sy.spacing) guides.push(...shiftGuides(sy.spacing.guides, dx, 0));\n\t\telse alignmentGuides(false);\n\t}\n\n\treturn {\n\t\tposition: { x: snapped.x, y: snapped.y },\n\t\toffset: { x: dx, y: dy },\n\t\tguides,\n\t\tsnappedX: sx.delta !== null,\n\t\tsnappedY: sy.delta !== null\n\t};\n}\n\n/** Spacing guides are computed before the other axis snaps; shift their cross coordinate to match. */\nfunction shiftGuides(guides: GuideLine[], dx: number, dy: number): GuideLine[] {\n\treturn guides.map((g) =>\n\t\tg.orientation === 'horizontal'\n\t\t\t? { ...g, y1: g.y1 + dy, y2: g.y2 + dy }\n\t\t\t: { ...g, x1: g.x1 + dx, x2: g.x2 + dx }\n\t);\n}\n\n/** Ids of the given nodes plus every descendant (children move with their parent). */\nexport function collectDescendantIds(nodes: CanvasNode[], ids: Iterable<string>): Set<string> {\n\tconst result = new Set(ids);\n\tlet grew = true;\n\twhile (grew) {\n\t\tgrew = false;\n\t\tfor (const node of nodes) {\n\t\t\tif (node.parentId && result.has(node.parentId) && !result.has(node.id)) {\n\t\t\t\tresult.add(node.id);\n\t\t\t\tgrew = true;\n\t\t\t}\n\t\t}\n\t}\n\treturn result;\n}\n\n/**\n * Builds a `GuideIndex` of every node except the dragged ones and their descendants.\n * Call once at drag start and pass it to `snapNodeDrag` on every move.\n */\nexport function createNodeGuideIndex(\n\tnodes: CanvasNode[],\n\tdraggingIds: Iterable<string>,\n\toptions: NodeRectOptions & { filter?: (node: CanvasNode) => boolean } = {}\n): GuideIndex {\n\tconst exclude = collectDescendantIds(nodes, draggingIds);\n\tconst lookup = createNodeLookup(nodes);\n\tconst rects: Rect[] = [];\n\tfor (const node of nodes) {\n\t\tif (exclude.has(node.id) || node.hidden === true) continue;\n\t\tif (options.filter && !options.filter(node)) continue;\n\t\trects.push(getNodeRect(node, lookup, options));\n\t}\n\treturn createGuideIndex(rects);\n}\n\nexport interface SnapNodeDragOptions extends SnapGuidesOptions, NodeRectOptions {\n\t/** Index from `createNodeGuideIndex`, reused across drag frames. Built on demand when omitted. */\n\tindex?: GuideIndex;\n}\n\nexport interface SnapNodeDragResult extends SnapResult {\n\t/** Position updates moving every dragged node by `offset` (node-relative coordinates). */\n\tupdates: CanvasNodeUpdate[];\n}\n\n/**\n * Snaps a drag of one or more nodes. The dragged nodes' bounding box is snapped against all\n * other nodes; the resulting offset is applied to each dragged node. Dragged nodes whose\n * parent is also dragged are skipped, because they move with the parent.\n */\nexport function snapNodeDrag(\n\tnodes: CanvasNode[],\n\tdraggingIds: Iterable<string>,\n\toptions: SnapNodeDragOptions = {}\n): SnapNodeDragResult {\n\tconst ids = new Set(draggingIds);\n\tconst lookup = createNodeLookup(nodes);\n\tconst dragged = [...ids].map((id) => lookup.get(id)).filter((n): n is CanvasNode => !!n);\n\tconst empty: SnapNodeDragResult = {\n\t\tposition: { x: 0, y: 0 },\n\t\toffset: { x: 0, y: 0 },\n\t\tguides: [],\n\t\tsnappedX: false,\n\t\tsnappedY: false,\n\t\tupdates: []\n\t};\n\tif (dragged.length === 0) return empty;\n\n\tconst bounds = boundsOf(dragged.map((n) => getNodeRect(n, lookup, options)))!;\n\tconst index = options.index ?? createNodeGuideIndex(nodes, ids, options);\n\tconst result = computeSnapGuides(bounds, index, options);\n\tconst { x: dx, y: dy } = result.offset;\n\tconst updates: CanvasNodeUpdate[] = [];\n\tif (dx !== 0 || dy !== 0) {\n\t\tfor (const node of dragged) {\n\t\t\tif (node.parentId && ids.has(node.parentId)) continue;\n\t\t\tupdates.push({ id: node.id, position: { x: node.position.x + dx, y: node.position.y + dy } });\n\t\t}\n\t}\n\treturn { ...result, updates };\n}\n","import { centre } from './layout.js';\nimport type { Position, Rect, Side, XYPosition } from './types.js';\n\n/**\n * Connection line styles supported by both SvelteFlow and React Flow\n * (`connectionLineType` / edge `type`).\n */\nexport type CanvasConnectionLineType = 'default' | 'straight' | 'step' | 'smoothstep' | 'simplebezier';\n\nexport const CONNECTION_LINE_TYPES: readonly CanvasConnectionLineType[] = [\n\t'default',\n\t'straight',\n\t'step',\n\t'smoothstep',\n\t'simplebezier'\n];\n\n/** Maps a handle `Position` to the placement `Side` vocabulary used by `placeBeside`. */\nexport function positionToSide(position: Position): Side {\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn 'above';\n\t\tcase 'bottom':\n\t\t\treturn 'below';\n\t\tdefault:\n\t\t\treturn position;\n\t}\n}\n\n/** Maps a placement `Side` to the handle `Position` vocabulary used by xyflow. */\nexport function sideToPosition(side: Side): Position {\n\tswitch (side) {\n\t\tcase 'above':\n\t\t\treturn 'top';\n\t\tcase 'below':\n\t\t\treturn 'bottom';\n\t\tdefault:\n\t\t\treturn side;\n\t}\n}\n\n/** The handle position facing the other way. */\nexport function oppositePosition(position: Position): Position {\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn 'bottom';\n\t\tcase 'bottom':\n\t\t\treturn 'top';\n\t\tcase 'left':\n\t\t\treturn 'right';\n\t\tdefault:\n\t\t\treturn 'left';\n\t}\n}\n\n/** Midpoint of a rectangle's side, where a centred handle sits. */\nexport function handlePoint(rect: Rect, position: Position): XYPosition {\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn { x: rect.x + rect.width / 2, y: rect.y };\n\t\tcase 'bottom':\n\t\t\treturn { x: rect.x + rect.width / 2, y: rect.y + rect.height };\n\t\tcase 'left':\n\t\t\treturn { x: rect.x, y: rect.y + rect.height / 2 };\n\t\tdefault:\n\t\t\treturn { x: rect.x + rect.width, y: rect.y + rect.height / 2 };\n\t}\n}\n\nexport interface ConnectorSnapTarget {\n\tid: string;\n\trect: Rect;\n\t/** Sides that can accept a connection (default: all four). */\n\tpositions?: Position[];\n}\n\nexport interface ConnectorSnapOptions {\n\t/** Viewport zoom; `radius` is in screen pixels and divided by it (default 1). */\n\tzoom?: number;\n\t/** How far outside a shape the pointer may be and still attach, in screen pixels (default 24). */\n\tradius?: number;\n\t/** Node ids that cannot be attached to, e.g. the connection's source node. */\n\texcludeIds?: Iterable<string>;\n\t/**\n\t * `handle` snaps to the side's midpoint (a centred handle, default). `edge` snaps to the\n\t * closest point along that side, for floating edges.\n\t */\n\tmode?: 'handle' | 'edge';\n}\n\nexport interface ConnectorSnap {\n\tnodeId: string;\n\tposition: Position;\n\tside: Side;\n\t/** Attachment point in flow coordinates. */\n\tpoint: XYPosition;\n\t/** Distance from the pointer to `point`, in flow units. */\n\tdistance: number;\n}\n\n/**\n * Finds the shape side a connection line should attach to for a pointer (flow coordinates).\n * Shapes the pointer is inside win over shapes it is merely near; among those, the closest\n * allowed side wins. Returns `null` when nothing is within `radius`.\n */\nexport function findConnectorSnap(\n\tpointer: XYPosition,\n\ttargets: ConnectorSnapTarget[],\n\toptions: ConnectorSnapOptions = {}\n): ConnectorSnap | null {\n\tconst zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;\n\tconst radius = (options.radius ?? 24) / zoom;\n\tconst exclude = new Set(options.excludeIds ?? []);\n\tconst all: Position[] = ['top', 'right', 'bottom', 'left'];\n\n\tlet best: (ConnectorSnap & { inside: boolean; rank: number }) | null = null;\n\tfor (const target of targets) {\n\t\tif (exclude.has(target.id)) continue;\n\t\tconst { rect } = target;\n\t\t// Cheap rejection: pointer outside the rect grown by the radius.\n\t\tif (\n\t\t\tpointer.x < rect.x - radius ||\n\t\t\tpointer.x > rect.x + rect.width + radius ||\n\t\t\tpointer.y < rect.y - radius ||\n\t\t\tpointer.y > rect.y + rect.height + radius\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst inside =\n\t\t\tpointer.x >= rect.x &&\n\t\t\tpointer.x <= rect.x + rect.width &&\n\t\t\tpointer.y >= rect.y &&\n\t\t\tpointer.y <= rect.y + rect.height;\n\n\t\tfor (const position of target.positions ?? all) {\n\t\t\tconst point =\n\t\t\t\toptions.mode === 'edge' ? closestPointOnSide(rect, position, pointer) : handlePoint(rect, position);\n\t\t\t// Rank sides by distance from the pointer to the side line, so a pointer near the\n\t\t\t// middle of a wide shape attaches to the side it is closest to.\n\t\t\tconst sidePoint = closestPointOnSide(rect, position, pointer);\n\t\t\tconst rank = Math.hypot(pointer.x - sidePoint.x, pointer.y - sidePoint.y);\n\t\t\tconst distance = Math.hypot(pointer.x - point.x, pointer.y - point.y);\n\t\t\tif (!inside && rank > radius) continue;\n\t\t\tconst better =\n\t\t\t\t!best ||\n\t\t\t\t(inside && !best.inside) ||\n\t\t\t\t(inside === best.inside && rank < best.rank);\n\t\t\tif (better) {\n\t\t\t\tbest = { nodeId: target.id, position, side: positionToSide(position), point, distance, inside, rank };\n\t\t\t}\n\t\t}\n\t}\n\tif (!best) return null;\n\tconst { inside: _inside, rank: _rank, ...snap } = best;\n\treturn snap;\n}\n\nfunction closestPointOnSide(rect: Rect, position: Position, p: XYPosition): XYPosition {\n\tconst clampX = Math.min(rect.x + rect.width, Math.max(rect.x, p.x));\n\tconst clampY = Math.min(rect.y + rect.height, Math.max(rect.y, p.y));\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn { x: clampX, y: rect.y };\n\t\tcase 'bottom':\n\t\t\treturn { x: clampX, y: rect.y + rect.height };\n\t\tcase 'left':\n\t\t\treturn { x: rect.x, y: clampY };\n\t\tdefault:\n\t\t\treturn { x: rect.x + rect.width, y: clampY };\n\t}\n}\n\n/**\n * Picks the facing sides for a connection between two shapes, the way Freeform attaches\n * connection lines: along the axis with the larger centre offset.\n */\nexport function nearestHandlePositions(\n\tsource: Rect,\n\ttarget: Rect\n): { source: Position; target: Position } {\n\tconst a = centre(source);\n\tconst b = centre(target);\n\tconst dx = b.x - a.x;\n\tconst dy = b.y - a.y;\n\tif (Math.abs(dx) >= Math.abs(dy)) {\n\t\treturn dx >= 0 ? { source: 'right', target: 'left' } : { source: 'left', target: 'right' };\n\t}\n\treturn dy >= 0 ? { source: 'bottom', target: 'top' } : { source: 'top', target: 'bottom' };\n}\n","import { boundsOf } from './layout.js';\nimport { createNodeLookup, getNodeRect, type NodeRectOptions } from './arrange.js';\nimport type { CanvasNode, Rect } from './types.js';\n\n/**\n * Viewport transform in xyflow's convention: `screen = flow * zoom + (x, y)`.\n */\nexport interface CanvasViewport {\n\tx: number;\n\ty: number;\n\tzoom: number;\n}\n\n/**\n * A named, saved view of the canvas (Freeform \"Scenes\").\n */\nexport interface CanvasScene {\n\tid: string;\n\tname: string;\n\tviewport: CanvasViewport;\n\t/** Optional nodes the scene is about, e.g. to re-fit the scene after they move. */\n\tnodeIds?: string[];\n\t/**\n\t * A provider transition effect (see `@pixerate/editor/effects`) played when the canvas moves\n\t * to this scene. Kept, and ignored, without an effects provider.\n\t */\n\ttransition?: CanvasSceneTransition;\n}\n\n/** A transition effect into a scene (see `CanvasScene.transition`). */\nexport interface CanvasSceneTransition {\n\t/** The provider's transition effect id. */\n\teffect: string;\n\tprovider?: string;\n\tname?: string;\n\t/** Control values (over the effect's defaults). */\n\tvalues: Record<string, number | string | boolean | readonly number[]>;\n\t/** Length in ms (default 600). */\n\tduration?: number;\n}\n\nexport interface CanvasScenesState {\n\tscenes: readonly CanvasScene[];\n\t/** Id of the scene last shown, or `null`. */\n\tcurrentId: string | null;\n\t/** Index of the current scene, or `-1`. */\n\tcurrentIndex: number;\n}\n\nexport interface CanvasScenesOptions {\n\tinitialScenes?: CanvasScene[];\n\t/** Id generator for new scenes (default: `crypto.randomUUID` with a counter fallback). */\n\tcreateId?: () => string;\n\t/** Whether `next` / `previous` wrap around at the ends (default `false`). */\n\twrap?: boolean;\n\tonChange?: (state: CanvasScenesState) => void;\n}\n\nexport interface SerializedCanvasScenes {\n\tversion: 1;\n\tscenes: CanvasScene[];\n}\n\nlet fallbackCounter = 0;\nfunction defaultId(): string {\n\tif (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n\t\treturn crypto.randomUUID();\n\t}\n\tfallbackCounter += 1;\n\treturn `scene-${Date.now().toString(36)}-${fallbackCounter}`;\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n\treturn typeof value === 'number' && Number.isFinite(value);\n}\n\n/** Validates the shape of a scene. */\nexport function isValidCanvasScene(value: unknown): value is CanvasScene {\n\tif (typeof value !== 'object' || value === null) return false;\n\tconst scene = value as Record<string, any>;\n\tconst vp = scene.viewport;\n\treturn (\n\t\ttypeof scene.id === 'string' &&\n\t\ttypeof scene.name === 'string' &&\n\t\ttypeof vp === 'object' &&\n\t\tvp !== null &&\n\t\tisFiniteNumber(vp.x) &&\n\t\tisFiniteNumber(vp.y) &&\n\t\tisFiniteNumber(vp.zoom) &&\n\t\tvp.zoom > 0 &&\n\t\t(scene.nodeIds === undefined ||\n\t\t\t(Array.isArray(scene.nodeIds) && scene.nodeIds.every((id: unknown) => typeof id === 'string')))\n\t);\n}\n\nfunction cloneScene(scene: CanvasScene): CanvasScene {\n\tconst copy: CanvasScene = {\n\t\tid: scene.id,\n\t\tname: scene.name,\n\t\tviewport: { x: scene.viewport.x, y: scene.viewport.y, zoom: scene.viewport.zoom }\n\t};\n\tif (scene.nodeIds) copy.nodeIds = [...scene.nodeIds];\n\tconst t = scene.transition;\n\tif (t && typeof t.effect === 'string' && t.effect) copy.transition = JSON.parse(JSON.stringify(t)) as CanvasSceneTransition;\n\treturn copy;\n}\n\n/**\n * Parses serialized scenes (a JSON string, a `{ version, scenes }` object or a bare array).\n * Invalid entries and duplicate ids are dropped.\n */\nexport function parseCanvasScenes(input: string | unknown): CanvasScene[] {\n\tlet value: unknown = input;\n\tif (typeof input === 'string') {\n\t\ttry {\n\t\t\tvalue = JSON.parse(input);\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\tconst list = Array.isArray(value)\n\t\t? value\n\t\t: typeof value === 'object' && value !== null && Array.isArray((value as any).scenes)\n\t\t\t? (value as any).scenes\n\t\t\t: [];\n\tconst seen = new Set<string>();\n\tconst scenes: CanvasScene[] = [];\n\tfor (const entry of list) {\n\t\tif (!isValidCanvasScene(entry) || seen.has(entry.id)) continue;\n\t\tseen.add(entry.id);\n\t\tscenes.push(cloneScene(entry));\n\t}\n\treturn scenes;\n}\n\n/**\n * Headless, framework-agnostic store of named viewports. Snapshots returned by `getState()`\n * are cached and only replaced on change, so it can back React's `useSyncExternalStore`.\n */\nexport class CanvasScenesStore {\n\tprivate scenes: CanvasScene[];\n\tprivate currentId: string | null = null;\n\tprivate cached: CanvasScenesState;\n\tprivate listeners = new Set<(state: CanvasScenesState) => void>();\n\tprivate readonly createId: () => string;\n\tprivate readonly onChange?: (state: CanvasScenesState) => void;\n\twrap: boolean;\n\n\tconstructor(options: CanvasScenesOptions = {}) {\n\t\tthis.scenes = parseCanvasScenes(options.initialScenes ?? []);\n\t\tthis.createId = options.createId ?? defaultId;\n\t\tthis.wrap = options.wrap ?? false;\n\t\tthis.onChange = options.onChange;\n\t\tthis.cached = this.snapshot();\n\t}\n\n\tprivate snapshot(): CanvasScenesState {\n\t\tconst currentIndex = this.currentId ? this.scenes.findIndex((s) => s.id === this.currentId) : -1;\n\t\treturn {\n\t\t\tscenes: this.scenes,\n\t\t\tcurrentId: currentIndex === -1 ? null : this.currentId,\n\t\t\tcurrentIndex\n\t\t};\n\t}\n\n\tprivate commit(scenes: CanvasScene[] = this.scenes) {\n\t\tthis.scenes = scenes;\n\t\tif (this.currentId && !scenes.some((s) => s.id === this.currentId)) this.currentId = null;\n\t\tthis.cached = this.snapshot();\n\t\tthis.onChange?.(this.cached);\n\t\tfor (const listener of this.listeners) listener(this.cached);\n\t}\n\n\tgetState(): CanvasScenesState {\n\t\treturn this.cached;\n\t}\n\n\tgetScene(id: string): CanvasScene | undefined {\n\t\treturn this.scenes.find((s) => s.id === id);\n\t}\n\n\tsubscribe(listener: (state: CanvasScenesState) => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.listeners.delete(listener);\n\t\t};\n\t}\n\n\t/** First unused \"Scene N\" name. */\n\tnextDefaultName(): string {\n\t\tconst names = new Set(this.scenes.map((s) => s.name));\n\t\tlet n = this.scenes.length + 1;\n\t\twhile (names.has(`Scene ${n}`)) n += 1;\n\t\treturn `Scene ${n}`;\n\t}\n\n\t/**\n\t * Adds a scene (appended, or inserted at `index`) and makes it current.\n\t */\n\tadd(input: { viewport: CanvasViewport; name?: string; nodeIds?: string[]; id?: string }, index?: number): CanvasScene {\n\t\tconst scene = cloneScene({\n\t\t\tid: input.id && !this.getScene(input.id) ? input.id : this.createId(),\n\t\t\tname: input.name?.trim() || this.nextDefaultName(),\n\t\t\tviewport: input.viewport,\n\t\t\tnodeIds: input.nodeIds\n\t\t});\n\t\tif (!isValidCanvasScene(scene)) throw new Error('[CanvasScenesStore] Invalid scene viewport.');\n\t\tconst scenes = [...this.scenes];\n\t\tconst at = index === undefined ? scenes.length : Math.max(0, Math.min(scenes.length, index));\n\t\tscenes.splice(at, 0, scene);\n\t\tthis.currentId = scene.id;\n\t\tthis.commit(scenes);\n\t\treturn scene;\n\t}\n\n\t/** Patches a scene's name, viewport or node ids. Returns the updated scene. */\n\tupdate(id: string, patch: Partial<Omit<CanvasScene, 'id'>>): CanvasScene | undefined {\n\t\tconst index = this.scenes.findIndex((s) => s.id === id);\n\t\tif (index === -1) return undefined;\n\t\tconst current = this.scenes[index];\n\t\tconst next = cloneScene({\n\t\t\t...current,\n\t\t\t...patch,\n\t\t\tname: patch.name !== undefined ? patch.name.trim() || current.name : current.name,\n\t\t\tviewport: patch.viewport ?? current.viewport,\n\t\t\tid\n\t\t});\n\t\tif (!isValidCanvasScene(next)) throw new Error('[CanvasScenesStore] Invalid scene viewport.');\n\t\tconst scenes = [...this.scenes];\n\t\tscenes[index] = next;\n\t\tthis.commit(scenes);\n\t\treturn next;\n\t}\n\n\trename(id: string, name: string): CanvasScene | undefined {\n\t\treturn this.update(id, { name });\n\t}\n\n\t/** Moves a scene to `toIndex` (clamped). */\n\tmove(id: string, toIndex: number): void {\n\t\tconst from = this.scenes.findIndex((s) => s.id === id);\n\t\tif (from === -1) return;\n\t\tconst to = Math.max(0, Math.min(this.scenes.length - 1, Math.trunc(toIndex)));\n\t\tif (from === to) return;\n\t\tconst scenes = [...this.scenes];\n\t\tconst [scene] = scenes.splice(from, 1);\n\t\tscenes.splice(to, 0, scene);\n\t\tthis.commit(scenes);\n\t}\n\n\t/** Reorders scenes to match `ids`; unknown ids are ignored and missing scenes keep their relative order at the end. */\n\treorder(ids: string[]): void {\n\t\tconst byId = new Map(this.scenes.map((s) => [s.id, s]));\n\t\tconst ordered: CanvasScene[] = [];\n\t\tfor (const id of ids) {\n\t\t\tconst scene = byId.get(id);\n\t\t\tif (scene) {\n\t\t\t\tordered.push(scene);\n\t\t\t\tbyId.delete(id);\n\t\t\t}\n\t\t}\n\t\tthis.commit([...ordered, ...this.scenes.filter((s) => byId.has(s.id))]);\n\t}\n\n\tremove(id: string): void {\n\t\tif (!this.getScene(id)) return;\n\t\tthis.commit(this.scenes.filter((s) => s.id !== id));\n\t}\n\n\tclear(): void {\n\t\tthis.currentId = null;\n\t\tthis.commit([]);\n\t}\n\n\t/** Marks a scene as current and returns it. */\n\tgoTo(id: string): CanvasScene | undefined {\n\t\tconst scene = this.getScene(id);\n\t\tif (!scene) return undefined;\n\t\tif (this.currentId !== id) {\n\t\t\tthis.currentId = id;\n\t\t\tthis.commit();\n\t\t}\n\t\treturn scene;\n\t}\n\n\tprivate step(direction: 1 | -1): CanvasScene | undefined {\n\t\tconst count = this.scenes.length;\n\t\tif (count === 0) return undefined;\n\t\tconst index = this.cached.currentIndex;\n\t\tlet next: number;\n\t\tif (index === -1) next = direction === 1 ? 0 : count - 1;\n\t\telse next = index + direction;\n\t\tif (next < 0 || next >= count) {\n\t\t\tif (!this.wrap) return undefined;\n\t\t\tnext = (next + count) % count;\n\t\t}\n\t\treturn this.goTo(this.scenes[next].id);\n\t}\n\n\t/** Shows the next scene. Returns `undefined` at the end unless `wrap` is on. */\n\tnext(): CanvasScene | undefined {\n\t\treturn this.step(1);\n\t}\n\n\t/** Shows the previous scene. Returns `undefined` at the start unless `wrap` is on. */\n\tprevious(): CanvasScene | undefined {\n\t\treturn this.step(-1);\n\t}\n\n\ttoJSON(): SerializedCanvasScenes {\n\t\treturn { version: 1, scenes: this.scenes.map(cloneScene) };\n\t}\n\n\tserialize(): string {\n\t\treturn JSON.stringify(this.toJSON());\n\t}\n\n\t/** Replaces all scenes from serialized data (see `parseCanvasScenes`). */\n\tload(input: string | unknown): void {\n\t\tthis.currentId = null;\n\t\tthis.commit(parseCanvasScenes(input));\n\t}\n}\n\nexport function createCanvasScenesStore(options?: CanvasScenesOptions): CanvasScenesStore {\n\treturn new CanvasScenesStore(options);\n}\n\nexport interface FitViewportOptions {\n\t/** Padding around the content as a fraction of the content size per side (default 0.1, like xyflow). */\n\tpadding?: number;\n\tminZoom?: number;\n\tmaxZoom?: number;\n}\n\n/**\n * Viewport that fits `rect` into a container of `size`, centred (xyflow `fitView` maths).\n */\nexport function fitViewportToRect(\n\trect: Rect,\n\tsize: { width: number; height: number },\n\toptions: FitViewportOptions = {}\n): CanvasViewport {\n\tconst { padding = 0.1, minZoom = 0.1, maxZoom = 2 } = options;\n\tconst paddedWidth = Math.max(rect.width * (1 + padding * 2), 1);\n\tconst paddedHeight = Math.max(rect.height * (1 + padding * 2), 1);\n\tconst raw = Math.min(size.width / paddedWidth, size.height / paddedHeight);\n\tconst zoom = Math.min(maxZoom, Math.max(minZoom, raw));\n\tconst cx = rect.x + rect.width / 2;\n\tconst cy = rect.y + rect.height / 2;\n\treturn { x: size.width / 2 - cx * zoom, y: size.height / 2 - cy * zoom, zoom };\n}\n\n/**\n * Viewport that fits the given nodes (all nodes when `ids` is omitted), or `null` when none match.\n */\nexport function fitViewportToNodes(\n\tnodes: CanvasNode[],\n\tsize: { width: number; height: number },\n\tids?: Iterable<string>,\n\toptions: FitViewportOptions & NodeRectOptions = {}\n): CanvasViewport | null {\n\tconst lookup = createNodeLookup(nodes);\n\tconst wanted = ids ? new Set(ids) : null;\n\tconst rects = nodes\n\t\t.filter((n) => (wanted ? wanted.has(n.id) : true))\n\t\t.map((n) => getNodeRect(n, lookup, options));\n\tconst bounds = boundsOf(rects);\n\treturn bounds ? fitViewportToRect(bounds, size, options) : null;\n}\n","/**\n * Shape nodes and Freeform-style shape boolean operations (Unite / Intersect / Subtract /\n * Exclude / Divide) on canvas nodes.\n *\n * A shape node is any node whose `data.shape` is a `ShapeDescription`. Its geometry is\n * relative to the node box, so moving or resizing the node moves / resizes the shape.\n */\nimport {\n\tcreateNodeLookup,\n\tgetAbsolutePosition,\n\tgetNodeSize,\n\tisLocked,\n\twithStyleSize,\n\tLOCKED_DATA_KEY,\n\tLOCKED_PREVIOUS_FLAGS_KEY,\n\ttype NodeRectOptions\n} from './arrange.js';\nimport {\n\tdividePolygons,\n\tmultiPolygonBounds,\n\tpolygonBoolean,\n\ttype MultiPolygon,\n\ttype PolygonBooleanOperation,\n\ttype PolygonFillRule,\n\ttype PolygonRing\n} from './polygonBoolean.js';\nimport {\n\tgetShapeSvg,\n\tisShapeDescription,\n\tpolygonToSvgPath,\n\tshapeToPolygon,\n\ttype PathShape,\n\ttype ShapeDescription,\n\ttype ShapeSize\n} from './shapeGeometry.js';\nimport {\n\tbezierPathBounds,\n\tbezierPathToSvg,\n\ttoBezierPath,\n\ttransformBezierPath,\n\ttype AffineMatrix,\n\ttype BezierPath\n} from './pathops/bezier.js';\nimport { dividePaths, pathBoolean, type PathOpsReport } from './pathops/boolean.js';\nimport { offsetPath, outlineStroke, type StrokeCap, type StrokeJoin } from './pathops/stroke.js';\nimport type { CanvasNode, Point } from './types.js';\n\n/** Data key holding a node's shape description. */\nexport const SHAPE_DATA_KEY = 'shape';\n\n/** Node type suggested for shape nodes (register `ShapeNode` under it). */\nexport const SHAPE_NODE_TYPE = 'shape';\n\n/** Freeform's shape operations. `exclude` is xor; `divide` splits into every region. */\nexport type ShapeOperation = 'unite' | 'intersect' | 'subtract' | 'exclude' | 'divide';\n\nexport const SHAPE_OPERATIONS: readonly ShapeOperation[] = ['unite', 'intersect', 'subtract', 'exclude', 'divide'];\n\nconst BOOLEAN_FOR: Record<Exclude<ShapeOperation, 'divide'>, PolygonBooleanOperation> = {\n\tunite: 'union',\n\tintersect: 'intersection',\n\tsubtract: 'difference',\n\texclude: 'xor'\n};\n\n/** Whether a node carries a supported `data.shape`. */\nexport function isShapeNode(node: CanvasNode | undefined | null): boolean {\n\treturn !!node && isShapeDescription(node.data?.[SHAPE_DATA_KEY]);\n}\n\n/** The node's shape description, or `null`. */\nexport function getNodeShape(node: CanvasNode | undefined | null): ShapeDescription | null {\n\tconst shape = node?.data?.[SHAPE_DATA_KEY];\n\treturn isShapeDescription(shape) ? shape : null;\n}\n\nexport interface ShapeNodePolygonOptions extends NodeRectOptions {\n\t/** Curve flattening tolerance in flow units (default 0.25). */\n\ttolerance?: number;\n}\n\n/**\n * A shape node's outline in absolute flow coordinates (parent offsets, size and rotation\n * applied), or `null` for non-shape nodes.\n */\nexport function shapeNodeToPolygon(\n\tnode: CanvasNode,\n\tlookup?: ReadonlyMap<string, CanvasNode>,\n\toptions: ShapeNodePolygonOptions = {}\n): { rings: PolygonRing[]; fillRule: PolygonFillRule } | null {\n\tconst shape = getNodeShape(node);\n\tif (!shape) return null;\n\tconst size = getNodeSize(node, options);\n\tconst origin = node.parentId && lookup ? getAbsolutePosition(node, lookup) : node.position;\n\tconst { rings, fillRule } = shapeToPolygon(shape, size, { tolerance: options.tolerance });\n\treturn {\n\t\trings: rings.map((ring) => ring.map(([x, y]) => [x + origin.x, y + origin.y] as Point)),\n\t\tfillRule\n\t};\n}\n\n/**\n * A shape as an exact Bézier path in node-local pixels (rotation and path view boxes\n * applied; arcs become cubics), with the fill rule it is read with.\n */\nexport function shapeToBezierPath(\n\tshape: ShapeDescription,\n\tsize: ShapeSize\n): { path: BezierPath; fillRule: PolygonFillRule } {\n\tconst { width: w, height: h } = size;\n\tif (!(w > 0) || !(h > 0)) return { path: [], fillRule: 'nonzero' };\n\tconst svg = getShapeSvg({ ...shape, rotation: 0 }, size);\n\tlet path = toBezierPath(svg.d);\n\tif (shape.kind === 'path' && shape.viewBox) {\n\t\tconst vb = shape.viewBox;\n\t\tif (vb.length === 4 && vb.every(Number.isFinite) && vb[2] > 0 && vb[3] > 0) {\n\t\t\tconst sx = w / vb[2];\n\t\t\tconst sy = h / vb[3];\n\t\t\tpath = transformBezierPath(path, [sx, 0, 0, sy, -vb[0] * sx, -vb[1] * sy]);\n\t\t}\n\t}\n\tconst deg = shape.rotation ?? 0;\n\tif (deg && Number.isFinite(deg)) {\n\t\tconst rad = (deg * Math.PI) / 180;\n\t\tconst cos = Math.cos(rad);\n\t\tconst sin = Math.sin(rad);\n\t\tconst cx = w / 2;\n\t\tconst cy = h / 2;\n\t\tconst m: AffineMatrix = [cos, sin, -sin, cos, cx - cx * cos + cy * sin, cy - cx * sin - cy * cos];\n\t\tpath = transformBezierPath(path, m);\n\t}\n\treturn { path, fillRule: svg.fillRule };\n}\n\n/**\n * A shape node's outline as an exact Bézier path in absolute flow coordinates (parent\n * offsets, size and rotation applied), or `null` for non-shape nodes.\n */\nexport function shapeNodeToBezierPath(\n\tnode: CanvasNode,\n\tlookup?: ReadonlyMap<string, CanvasNode>,\n\toptions: NodeRectOptions = {}\n): { path: BezierPath; fillRule: PolygonFillRule } | null {\n\tconst shape = getNodeShape(node);\n\tif (!shape) return null;\n\tconst size = getNodeSize(node, options);\n\tconst origin = node.parentId && lookup ? getAbsolutePosition(node, lookup) : node.position;\n\tconst { path, fillRule } = shapeToBezierPath(shape, size);\n\treturn { path: transformBezierPath(path, [1, 0, 0, 1, origin.x, origin.y]), fillRule };\n}\n\n/** Why `combineShapeNodes` refused. */\nexport type CombineShapeNodesRefusal =\n\t/** A selected shape is locked; locked shapes are never consumed. */\n\t| 'locked'\n\t/** Fewer than two shape nodes were selected. */\n\t| 'too-few'\n\t/** The operation leaves nothing (e.g. intersecting shapes that do not overlap). */\n\t| 'empty';\n\nexport interface CombineShapeNodesOptions extends ShapeNodePolygonOptions {\n\t/**\n\t * Which shape's node style and data the result inherits: `bottom` (default, like\n\t * Freeform: the back-most shape), `top`, `first` (first id passed), or a node id.\n\t * Divide pieces always take the style of the top-most shape covering them.\n\t */\n\tstyleFrom?: 'bottom' | 'top' | 'first' | { id: string };\n\t/** Id factory for new nodes (default `crypto.randomUUID`, with a counter fallback). */\n\tcreateId?: (index: number) => string;\n\t/** Node `type` for the new nodes (default: the style source's type). */\n\tnodeType?: string;\n\t/** Decimal places in the path data (default 3). */\n\tprecision?: number;\n\t/** Ramer–Douglas–Peucker simplification tolerance for the result (default 0, off). */\n\tsimplify?: number;\n\t/**\n\t * Geometry engine. `'curve'` (default) computes the operation on the exact Bézier\n\t * outlines and keeps the inputs' curves (circles stay cubic arcs, rectangles stay four\n\t * lines); it falls back to `'polygon'` when it cannot resolve the input (reported as\n\t * `engine` / `fallbackReason` on the result). `'polygon'` flattens the shapes and uses\n\t * `polygonBoolean` (the behaviour before curve booleans). `curves: 'lines'` implies\n\t * `'polygon'` unless an engine is named.\n\t */\n\tengine?: 'curve' | 'polygon';\n\t/** Called with the reason when the curve engine falls back to polygons. */\n\tonFallback?: (reason: string) => void;\n\t/**\n\t * Polygon engine only: how the result's outline is written: `'fit'` (default) fits\n\t * smooth cubic curves to the flattened result (sharp corners and straight edges are\n\t * kept); `'lines'` writes the polygon as straight segments.\n\t */\n\tcurves?: 'lines' | 'fit';\n\t/** Curve-fitting tolerance in flow units for `curves: 'fit'` (default: `tolerance`, 0.25). */\n\tcurveTolerance?: number;\n\t/** Corner threshold in degrees for `curves: 'fit'` (default 30). */\n\tcornerAngleDeg?: number;\n\t/** Point-merge tolerance passed to the boolean engine. */\n\tepsilon?: number;\n\t/** Select the new nodes (default `true`). */\n\tselect?: boolean;\n}\n\nexport type CombineShapeNodesResult<TNode extends CanvasNode = CanvasNode> =\n\t| {\n\t\t\tok: true;\n\t\t\toperation: ShapeOperation;\n\t\t\t/** Ids of the shape nodes consumed by the operation. */\n\t\t\tremoveIds: string[];\n\t\t\t/** New path shape node(s): one, or one per region for `divide`. */\n\t\t\tnodes: TNode[];\n\t\t\t/** Engine that produced the geometry (`polygon` after a fallback). */\n\t\t\tengine: 'curve' | 'polygon';\n\t\t\t/** Why the curve engine fell back to polygons. */\n\t\t\tfallbackReason?: string;\n\t }\n\t| { ok: false; operation: ShapeOperation; reason: CombineShapeNodesRefusal };\n\nlet idCounter = 0;\nfunction defaultId(index: number): string {\n\tif (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') return crypto.randomUUID();\n\tidCounter += 1;\n\treturn `shape-${Date.now().toString(36)}-${idCounter}-${index}`;\n}\n\n/** Back-to-front order: `zIndex` (missing = 0), then array order, as xyflow stacks nodes. */\nfunction stackOrder<TNode extends CanvasNode>(nodes: TNode[], ids: Set<string>): TNode[] {\n\treturn nodes\n\t\t.map((node, index) => ({ node, index, z: typeof node.zIndex === 'number' ? node.zIndex : 0 }))\n\t\t.filter((entry) => ids.has(entry.node.id))\n\t\t.sort((a, b) => a.z - b.z || a.index - b.index)\n\t\t.map((entry) => entry.node);\n}\n\n/**\n * Checks whether `combineShapeNodes` can run, without computing geometry (it cannot\n * predict an `empty` result). Use it to enable menu items.\n */\nexport function canCombineShapeNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>\n): { ok: true; shapeIds: string[] } | { ok: false; reason: Exclude<CombineShapeNodesRefusal, 'empty'> } {\n\tconst idSet = new Set(ids);\n\tconst shapes = nodes.filter((n) => idSet.has(n.id) && isShapeNode(n));\n\tif (shapes.some((n) => isLocked(n))) return { ok: false, reason: 'locked' };\n\tif (shapes.length < 2) return { ok: false, reason: 'too-few' };\n\treturn { ok: true, shapeIds: shapes.map((n) => n.id) };\n}\n\n/**\n * Freeform-style shape operations on canvas nodes.\n *\n * - Only shape nodes (`data.shape`) among `ids` take part; other selected nodes are ignored.\n * - Refuses (`ok: false`) when a selected shape is locked, when fewer than two shapes are\n * selected, or when the result is empty. Nothing should be changed on refusal.\n * - `subtract` removes every other shape from the back-most one; the other operations are\n * symmetric. `divide` returns one node per distinct region of the overlay.\n * - Each new node is a `path` shape: its position / size is the result's bounding box and\n * its path data is node-local with `viewBox = [0, 0, width, height]`, so it scales with\n * the node. Rotation is baked into the path. Node fields and `data` (other than the\n * shape geometry, lock state and size) come from the style source.\n * - When all shapes share a parent, the new nodes keep it; otherwise they are top-level.\n *\n * Pure: returns the change; apply it with `applyShapeCombination` (or the arrange\n * controller's `combineShapes`, which also records one undo step).\n */\nexport function combineShapeNodes<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tids: Iterable<string>,\n\toperation: ShapeOperation,\n\toptions: CombineShapeNodesOptions = {}\n): CombineShapeNodesResult<TNode> {\n\tconst idList = [...ids];\n\tconst check = canCombineShapeNodes(nodes, idList);\n\tif (!check.ok) return { ok: false, operation, reason: check.reason };\n\n\tconst lookup = createNodeLookup(nodes);\n\tconst shapeIds = new Set(check.shapeIds);\n\tconst ordered = stackOrder(nodes, shapeIds);\n\n\tconst parentIds = new Set(ordered.map((n) => n.parentId ?? ''));\n\tconst sharedParent = parentIds.size === 1 ? ordered[0].parentId : undefined;\n\tconst parentOrigin =\n\t\tsharedParent && lookup.get(sharedParent)\n\t\t\t? getAbsolutePosition(lookup.get(sharedParent)!, lookup)\n\t\t\t: { x: 0, y: 0 };\n\n\tconst pickSource = (): TNode => {\n\t\tconst style = options.styleFrom ?? 'bottom';\n\t\tif (style === 'top') return ordered[ordered.length - 1];\n\t\tif (style === 'first') {\n\t\t\tconst first = idList.find((id) => shapeIds.has(id));\n\t\t\treturn (first && lookup.get(first)) || ordered[0];\n\t\t}\n\t\tif (typeof style === 'object') return (shapeIds.has(style.id) && lookup.get(style.id)) || ordered[0];\n\t\treturn ordered[0];\n\t};\n\n\tconst createId = options.createId ?? defaultId;\n\tconst buildOptions: BuildPathNodeOptions = {\n\t\tprecision: options.precision,\n\t\tnodeType: options.nodeType,\n\t\tselect: options.select,\n\t\tparentOrigin,\n\t\tsharedParent\n\t};\n\tconst engine = options.engine ?? (options.curves === 'lines' ? 'polygon' : 'curve');\n\n\tconst created: TNode[] = [];\n\tlet report: PathOpsReport = { engine: 'curve' };\n\tif (engine === 'curve') {\n\t\tconst operands = ordered.map((node) => shapeNodeToBezierPath(node, lookup, options)!);\n\t\tconst pathOptions = { epsilon: options.epsilon, onFallback: options.onFallback, fallbackTolerance: options.tolerance };\n\t\tif (operation === 'divide') {\n\t\t\tconst result = dividePaths(operands, pathOptions);\n\t\t\treport = result;\n\t\t\tfor (const piece of result.pieces) {\n\t\t\t\tconst source = ordered[piece.operands[piece.operands.length - 1]];\n\t\t\t\tconst node = buildPathNode(piece.path, source, createId(created.length), buildOptions);\n\t\t\t\tif (node) created.push(node);\n\t\t\t}\n\t\t} else {\n\t\t\tconst result = pathBoolean(BOOLEAN_FOR[operation], operands, pathOptions);\n\t\t\treport = result;\n\t\t\tconst node = buildPathNode(result.path, pickSource(), createId(0), buildOptions);\n\t\t\tif (node) created.push(node);\n\t\t}\n\t} else {\n\t\treport = { engine: 'polygon' };\n\t\tconst operands = ordered.map((node) => shapeNodeToPolygon(node, lookup, options)!);\n\t\tconst write = (polygon: MultiPolygon): BezierPath =>\n\t\t\ttoBezierPath(\n\t\t\t\tpolygonToSvgPath(polygon, {\n\t\t\t\t\tprecision: 9,\n\t\t\t\t\tsimplify: options.simplify,\n\t\t\t\t\tcurves: options.curves ?? 'fit',\n\t\t\t\t\tcurveTolerance: options.curveTolerance ?? options.tolerance ?? 0.25,\n\t\t\t\t\tcornerAngleDeg: options.cornerAngleDeg\n\t\t\t\t})\n\t\t\t);\n\t\tif (operation === 'divide') {\n\t\t\tfor (const piece of dividePolygons(operands, { epsilon: options.epsilon })) {\n\t\t\t\tconst source = ordered[piece.operands[piece.operands.length - 1]];\n\t\t\t\tconst node = buildPathNode(write([piece.polygon]), source, createId(created.length), buildOptions);\n\t\t\t\tif (node) created.push(node);\n\t\t\t}\n\t\t} else {\n\t\t\tconst result = polygonBoolean(BOOLEAN_FOR[operation], operands, { epsilon: options.epsilon });\n\t\t\tconst node = buildPathNode(write(result), pickSource(), createId(0), buildOptions);\n\t\t\tif (node) created.push(node);\n\t\t}\n\t}\n\tif (created.length === 0) return { ok: false, operation, reason: 'empty' };\n\tconst done: CombineShapeNodesResult<TNode> = {\n\t\tok: true,\n\t\toperation,\n\t\tremoveIds: ordered.map((n) => n.id),\n\t\tnodes: created,\n\t\tengine: report.engine\n\t};\n\tif (report.fallbackReason) done.fallbackReason = report.fallbackReason;\n\treturn done;\n}\n\ninterface BuildPathNodeOptions {\n\tprecision?: number;\n\tnodeType?: string;\n\tselect?: boolean;\n\tparentOrigin: { x: number; y: number };\n\tsharedParent?: string;\n\t/** Paint overrides; `null` removes the key (e.g. an outlined stroke has no stroke). */\n\tpaint?: Partial<Record<'fill' | 'stroke' | 'strokeWidth' | 'opacity', string | number | null>>;\n}\n\n/**\n * A new `path` shape node for an absolute path: positioned at the path's bounding box with\n * node-local path data and `viewBox = [0, 0, width, height]`; node fields, data and paint\n * come from `source`. `null` when the path is empty or flat.\n */\nfunction buildPathNode<TNode extends CanvasNode>(\n\tpath: BezierPath,\n\tsource: TNode,\n\tid: string,\n\toptions: BuildPathNodeOptions\n): TNode | null {\n\tconst bounds = bezierPathBounds(path);\n\tif (!bounds) return null;\n\tconst width = bounds.maxX - bounds.minX;\n\tconst height = bounds.maxY - bounds.minY;\n\tif (!(width > 0) || !(height > 0)) return null;\n\tconst d = bezierPathToSvg(path, { precision: options.precision ?? 3, offset: { x: -bounds.minX, y: -bounds.minY } });\n\tif (!d) return null;\n\tconst sourceShape = getNodeShape(source)!;\n\tconst shape: PathShape = {\n\t\tkind: 'path',\n\t\td,\n\t\tviewBox: [0, 0, roundSize(width), roundSize(height)],\n\t\tfillRule: 'nonzero'\n\t};\n\tfor (const key of ['fill', 'stroke', 'strokeWidth', 'opacity'] as const) {\n\t\tif (sourceShape[key] !== undefined) (shape as any)[key] = sourceShape[key];\n\t}\n\tfor (const [key, value] of Object.entries(options.paint ?? {})) {\n\t\tif (value === null) delete (shape as any)[key];\n\t\telse if (value !== undefined) (shape as any)[key] = value;\n\t}\n\tconst {\n\t\t[LOCKED_DATA_KEY]: _locked,\n\t\t[LOCKED_PREVIOUS_FLAGS_KEY]: _previous,\n\t\t...data\n\t} = source.data ?? {};\n\tconst {\n\t\tmeasured: _measured,\n\t\tselected: _selected,\n\t\tdragging: _dragging,\n\t\tpositionAbsolute: _positionAbsolute,\n\t\tinternals: _internals,\n\t\tparentId: _parentId,\n\t\t...rest\n\t} = source as CanvasNode;\n\tconst node: CanvasNode = {\n\t\t...rest,\n\t\tid,\n\t\tposition: { x: bounds.minX - options.parentOrigin.x, y: bounds.minY - options.parentOrigin.y },\n\t\twidth,\n\t\theight,\n\t\tdata: { ...data, [SHAPE_DATA_KEY]: shape }\n\t};\n\tif (options.sharedParent) node.parentId = options.sharedParent;\n\tif (options.nodeType !== undefined) node.type = options.nodeType;\n\tif (source.style) node.style = withStyleSize(source.style, { width, height });\n\tif (options.select !== false) node.selected = true;\n\treturn node as TNode;\n}\n\nfunction roundSize(value: number): number {\n\treturn Math.round(value * 1000) / 1000;\n}\n\n/**\n * Applies a successful `combineShapeNodes` result: removes the consumed nodes and inserts\n * the new ones at the array index of the first consumed node.\n * With `deselect` (default `true`) other nodes are deselected when the result is selected.\n */\nexport function applyShapeCombination<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tchange: { removeIds: Iterable<string>; nodes: TNode[] },\n\toptions: { deselect?: boolean } = {}\n): TNode[] {\n\tconst remove = new Set(change.removeIds);\n\tconst insertAt = nodes.findIndex((n) => remove.has(n.id));\n\tconst deselect = options.deselect !== false && change.nodes.some((n) => n.selected);\n\tconst out: TNode[] = [];\n\tnodes.forEach((node, index) => {\n\t\tif (index === insertAt) out.push(...change.nodes);\n\t\tif (remove.has(node.id)) return;\n\t\tout.push(deselect && node.selected ? ({ ...node, selected: false } as TNode) : node);\n\t});\n\tif (insertAt === -1) out.push(...change.nodes);\n\treturn out;\n}\n\n/**\n * Reverses `applyShapeCombination`: removes the created nodes and puts the consumed nodes\n * back at their original array indices (`previous` is the node array before the change).\n */\nexport function revertShapeCombination<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tchange: { removeIds: Iterable<string>; nodes: TNode[] },\n\tprevious: TNode[]\n): TNode[] {\n\tconst created = new Set(change.nodes.map((n) => n.id));\n\tconst removed = new Set(change.removeIds);\n\tconst out = nodes.filter((n) => !created.has(n.id));\n\tprevious.forEach((node, index) => {\n\t\tif (!removed.has(node.id) || out.some((n) => n.id === node.id)) return;\n\t\tout.splice(Math.min(index, out.length), 0, node);\n\t});\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// Offset Path and Outline Stroke on shape nodes\n// ---------------------------------------------------------------------------\n\n/** Per-node path edits: `offset` (Offset Path) and `outlineStroke` (Outline Stroke). */\nexport type ShapePathEditOperation = 'offset' | 'outlineStroke';\n\n/** Why `offsetShapeNodes` / `outlineShapeNodeStrokes` refused. */\nexport type ShapePathEditRefusal =\n\t/** A selected shape is locked. */\n\t| 'locked'\n\t/** No shape node was selected. */\n\t| 'no-shapes'\n\t/** Outline Stroke: no selected shape has a visible stroke. */\n\t| 'no-stroke'\n\t/** Every result is empty (e.g. an inset larger than the shapes). */\n\t| 'empty'\n\t/** The offset distance or stroke width is not a finite number. */\n\t| 'invalid';\n\nexport type ShapePathEditResult<TNode extends CanvasNode = CanvasNode> =\n\t| {\n\t\t\tok: true;\n\t\t\toperation: ShapePathEditOperation;\n\t\t\t/** Ids of the replaced nodes. */\n\t\t\tremoveIds: string[];\n\t\t\t/** All new nodes (a replaced node may come back with the same id). */\n\t\t\tnodes: TNode[];\n\t\t\t/** Each replaced node with the nodes that take its place, in stacking order. */\n\t\t\treplacements: { id: string; nodes: TNode[] }[];\n\t }\n\t| { ok: false; operation: ShapePathEditOperation; reason: ShapePathEditRefusal };\n\ninterface ShapePathEditBaseOptions extends NodeRectOptions {\n\t/** Id factory for new nodes (default `crypto.randomUUID`, with a counter fallback). */\n\tcreateId?: (index: number) => string;\n\t/** Node `type` for the new nodes (default: the source node's type). */\n\tnodeType?: string;\n\t/** Decimal places in the path data (default 3). */\n\tprecision?: number;\n\t/** Select the new nodes (default `true`). */\n\tselect?: boolean;\n\t/** Maximum error of the approximated offset curves in flow units (default 0.01). */\n\ttolerance?: number;\n\t/** Point-merge tolerance passed to the boolean engine. */\n\tepsilon?: number;\n\t/** Called with the reason when the curve engine falls back to polygons. */\n\tonFallback?: (reason: string) => void;\n}\n\nexport interface OffsetShapeNodesOptions extends ShapePathEditBaseOptions {\n\t/** Corner style of the offset outline (default `miter`). */\n\tjoin?: StrokeJoin;\n\t/** Miter limit (default 4). */\n\tmiterLimit?: number;\n\t/** Keep the original shape above the offset one (Illustrator style; default `false`: replace it). */\n\tkeepOriginal?: boolean;\n}\n\nexport interface OutlineShapeStrokesOptions extends ShapePathEditBaseOptions {\n\t/** Stroke width in flow units (default: the shape's `strokeWidth`, 1 when unset). */\n\twidth?: number;\n\t/** Default `miter`, as SVG. */\n\tjoin?: StrokeJoin;\n\t/** Default `butt`, as SVG. */\n\tcap?: StrokeCap;\n\t/** Default 4, as SVG. */\n\tmiterLimit?: number;\n\t/** Dash pattern (as `stroke-dasharray`). */\n\tdash?: readonly number[];\n\tdashOffset?: number;\n\t/**\n\t * Keep the shape's fill as the original node with its stroke removed, below the outlined\n\t * stroke (default `true`). With `false`, or when the shape has no visible fill, the node is\n\t * replaced by the outlined stroke alone.\n\t */\n\tkeepFill?: boolean;\n}\n\nconst INVISIBLE_PAINT = new Set(['none', 'transparent']);\n\n/** Whether a shape draws a stroke (a stroke colour or gradient other than `none` and a positive width). */\nexport function hasVisibleStroke(shape: ShapeDescription | null): boolean {\n\tif (!shape) return false;\n\tconst stroke = (shape as any).strokeGradient ?? shape.stroke;\n\tif (stroke === undefined || stroke === null) return false;\n\tif (typeof stroke === 'string' && INVISIBLE_PAINT.has(stroke.trim().toLowerCase())) return false;\n\tconst width = shape.strokeWidth ?? 1;\n\treturn Number.isFinite(width) && width > 0;\n}\n\nfunction hasVisibleFill(shape: ShapeDescription): boolean {\n\tif ((shape as any).fillGradient) return true;\n\tif (shape.fill === undefined) return true;\n\tif (typeof shape.fill === 'object' && shape.fill !== null) return true;\n\treturn !INVISIBLE_PAINT.has(String(shape.fill).trim().toLowerCase());\n}\n\nfunction shapeTargets(nodes: CanvasNode[], ids: Iterable<string>) {\n\tconst idSet = new Set(ids);\n\treturn nodes.filter((n) => idSet.has(n.id) && isShapeNode(n));\n}\n\n/** Checks whether `offsetShapeNodes` can run (it cannot predict an `empty` result). */\nexport function canOffsetShapeNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>\n): { ok: true; shapeIds: string[] } | { ok: false; reason: 'locked' | 'no-shapes' } {\n\tconst shapes = shapeTargets(nodes, ids);\n\tif (shapes.some((n) => isLocked(n))) return { ok: false, reason: 'locked' };\n\tif (shapes.length === 0) return { ok: false, reason: 'no-shapes' };\n\treturn { ok: true, shapeIds: shapes.map((n) => n.id) };\n}\n\n/** Checks whether `outlineShapeNodeStrokes` can run. */\nexport function canOutlineShapeNodeStrokes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>\n): { ok: true; shapeIds: string[] } | { ok: false; reason: 'locked' | 'no-shapes' | 'no-stroke' } {\n\tconst shapes = shapeTargets(nodes, ids);\n\tif (shapes.some((n) => isLocked(n))) return { ok: false, reason: 'locked' };\n\tif (shapes.length === 0) return { ok: false, reason: 'no-shapes' };\n\tconst stroked = shapes.filter((n) => hasVisibleStroke(getNodeShape(n)));\n\tif (stroked.length === 0) return { ok: false, reason: 'no-stroke' };\n\treturn { ok: true, shapeIds: stroked.map((n) => n.id) };\n}\n\nfunction editPlacement(node: CanvasNode, lookup: Map<string, CanvasNode>) {\n\tconst parent = node.parentId ? lookup.get(node.parentId) : undefined;\n\treturn {\n\t\tparentOrigin: parent ? getAbsolutePosition(parent, lookup) : { x: 0, y: 0 },\n\t\tsharedParent: parent ? node.parentId : undefined\n\t};\n}\n\nfunction finishEdit<TNode extends CanvasNode>(\n\toperation: ShapePathEditOperation,\n\treplacements: { id: string; nodes: TNode[] }[]\n): ShapePathEditResult<TNode> {\n\tif (replacements.length === 0) return { ok: false, operation, reason: 'empty' };\n\treturn {\n\t\tok: true,\n\t\toperation,\n\t\tremoveIds: replacements.map((r) => r.id),\n\t\tnodes: replacements.flatMap((r) => r.nodes),\n\t\treplacements\n\t};\n}\n\n/**\n * Offset Path on shape nodes: each selected shape grows (`distance > 0`) or shrinks\n * (`distance < 0`) by `distance` flow units, keeping curves. The result replaces the shape\n * (or sits below it with `keepOriginal`) as a `path` shape node with the same paint. Shapes\n * whose offset is empty are left unchanged; refuses when every result is empty, a selected\n * shape is locked or no shape is selected. Pure: apply with `applyShapeReplacement`.\n */\nexport function offsetShapeNodes<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tids: Iterable<string>,\n\tdistance: number,\n\toptions: OffsetShapeNodesOptions = {}\n): ShapePathEditResult<TNode> {\n\tconst operation = 'offset';\n\tif (!Number.isFinite(distance)) return { ok: false, operation, reason: 'invalid' };\n\tconst check = canOffsetShapeNodes(nodes, ids);\n\tif (!check.ok) return { ok: false, operation, reason: check.reason };\n\tconst lookup = createNodeLookup(nodes);\n\tconst createId = options.createId ?? defaultId;\n\tlet index = 0;\n\tconst replacements: { id: string; nodes: TNode[] }[] = [];\n\tfor (const id of check.shapeIds) {\n\t\tconst node = lookup.get(id) as TNode;\n\t\tconst outline = shapeNodeToBezierPath(node, lookup, options)!;\n\t\tconst path = offsetPath(outline.path, distance, {\n\t\t\tjoin: options.join,\n\t\t\tmiterLimit: options.miterLimit,\n\t\t\tfillRule: outline.fillRule,\n\t\t\ttolerance: options.tolerance,\n\t\t\tepsilon: options.epsilon,\n\t\t\tonFallback: options.onFallback\n\t\t});\n\t\tconst created = buildPathNode(path, node, createId(index), {\n\t\t\t...editPlacement(node, lookup),\n\t\t\tprecision: options.precision,\n\t\t\tnodeType: options.nodeType,\n\t\t\tselect: options.select\n\t\t});\n\t\tif (!created) continue;\n\t\tindex++;\n\t\tconst original = { ...node, selected: false } as TNode;\n\t\treplacements.push({ id, nodes: options.keepOriginal ? [created, original] : [created] });\n\t}\n\treturn finishEdit(operation, replacements);\n}\n\n/**\n * Outline Stroke on shape nodes: each selected shape with a visible stroke gets its stroke\n * converted to a filled `path` shape node (filled with the stroke colour, no stroke), using\n * the stroke width in flow units (strokes do not scale with the node) and SVG's default\n * miter join, miter limit 4 and butt caps unless overridden. The shape's fill stays as the\n * original node without its stroke, below the outline (`keepFill`). Pure: apply with\n * `applyShapeReplacement`.\n */\nexport function outlineShapeNodeStrokes<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tids: Iterable<string>,\n\toptions: OutlineShapeStrokesOptions = {}\n): ShapePathEditResult<TNode> {\n\tconst operation = 'outlineStroke';\n\tif (options.width !== undefined && !(Number.isFinite(options.width) && options.width > 0)) {\n\t\treturn { ok: false, operation, reason: 'invalid' };\n\t}\n\tconst check = canOutlineShapeNodeStrokes(nodes, ids);\n\tif (!check.ok) return { ok: false, operation, reason: check.reason };\n\tconst lookup = createNodeLookup(nodes);\n\tconst createId = options.createId ?? defaultId;\n\tlet index = 0;\n\tconst replacements: { id: string; nodes: TNode[] }[] = [];\n\tfor (const id of check.shapeIds) {\n\t\tconst node = lookup.get(id) as TNode;\n\t\tconst shape = getNodeShape(node)!;\n\t\tconst outline = shapeNodeToBezierPath(node, lookup, options)!;\n\t\tconst path = outlineStroke(outline.path, {\n\t\t\twidth: options.width ?? shape.strokeWidth ?? 1,\n\t\t\tjoin: options.join,\n\t\t\tcap: options.cap,\n\t\t\tmiterLimit: options.miterLimit,\n\t\t\tdash: options.dash,\n\t\t\tdashOffset: options.dashOffset,\n\t\t\ttolerance: options.tolerance,\n\t\t\tepsilon: options.epsilon,\n\t\t\tonFallback: options.onFallback\n\t\t});\n\t\tconst created = buildPathNode(path, node, createId(index), {\n\t\t\t...editPlacement(node, lookup),\n\t\t\tprecision: options.precision,\n\t\t\tnodeType: options.nodeType,\n\t\t\tselect: options.select,\n\t\t\tpaint: { fill: shape.stroke as string, stroke: null, strokeWidth: null }\n\t\t});\n\t\tif (!created) continue;\n\t\tindex++;\n\t\tconst replacement: TNode[] = [];\n\t\tif (options.keepFill !== false && hasVisibleFill(shape)) {\n\t\t\tconst { stroke: _stroke, strokeWidth: _width, ...rest } = shape;\n\t\t\treplacement.push({\n\t\t\t\t...node,\n\t\t\t\tselected: options.select === false ? node.selected : false,\n\t\t\t\tdata: { ...node.data, [SHAPE_DATA_KEY]: rest }\n\t\t\t} as TNode);\n\t\t}\n\t\treplacement.push(created);\n\t\treplacements.push({ id, nodes: replacement });\n\t}\n\treturn finishEdit(operation, replacements);\n}\n\n/**\n * Applies a successful `offsetShapeNodes` / `outlineShapeNodeStrokes` result: each replaced\n * node is swapped in place for its replacement nodes, so the stacking order of everything\n * else is kept. With `deselect` (default `true`) other nodes are deselected when the\n * result is selected. Undo with `revertShapeCombination(nodes, result, previous)`.\n */\nexport function applyShapeReplacement<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tchange: { replacements: { id: string; nodes: TNode[] }[] },\n\toptions: { deselect?: boolean } = {}\n): TNode[] {\n\tconst byId = new Map(change.replacements.map((r) => [r.id, r.nodes]));\n\tconst created = new Set(change.replacements.flatMap((r) => r.nodes.map((n) => n.id)));\n\tconst deselect = options.deselect !== false && change.replacements.some((r) => r.nodes.some((n) => n.selected));\n\tconst out: TNode[] = [];\n\tfor (const node of nodes) {\n\t\tconst replacement = byId.get(node.id);\n\t\tif (replacement) {\n\t\t\tout.push(...replacement);\n\t\t\tcontinue;\n\t\t}\n\t\tif (created.has(node.id)) continue;\n\t\tout.push(deselect && node.selected ? ({ ...node, selected: false } as TNode) : node);\n\t}\n\treturn out;\n}\n","/**\n * Resize snapping: the moving edges of a box being resized land on other shapes' edges and\n * centres (or on a grid) while the opposite edge / corner stays put.\n *\n * Portions ported from vectorcraft-main/crates/tools/src/guides.rs (`snap_scale`) and\n * vectorcraft-main/crates/geom/src/snap.rs (MIT OR Apache-2.0, © ArtCraft Team and\n * contributors). See THIRD_PARTY_NOTICES.md.\n */\nimport { boundsOf } from './layout.js';\nimport {\n\tcreateNodeLookup,\n\tgetAbsolutePosition,\n\tgetNodeSize,\n\tisLocked,\n\ttype CanvasNodeUpdate,\n\ttype NodeRectOptions\n} from './arrange.js';\nimport {\n\tcreateGuideIndex,\n\tcreateNodeGuideIndex,\n\tsnapToGrid,\n\ttype GuideIndex,\n\ttype GuideLine,\n\ttype SnapGuidesOptions\n} from './guides.js';\nimport type { CanvasNode, Rect } from './types.js';\n\n/** A resize handle, named like xyflow's `ControlPosition`. */\nexport type ResizeHandle =\n\t| 'top-left'\n\t| 'top'\n\t| 'top-right'\n\t| 'right'\n\t| 'bottom-right'\n\t| 'bottom'\n\t| 'bottom-left'\n\t| 'left';\n\nexport const RESIZE_HANDLES: readonly ResizeHandle[] = [\n\t'top-left',\n\t'top',\n\t'top-right',\n\t'right',\n\t'bottom-right',\n\t'bottom',\n\t'bottom-left',\n\t'left'\n];\n\n/** Which edge of each axis a handle moves: -1 the start (left / top), 1 the end, 0 none. */\nexport function resizeHandleAxes(handle: ResizeHandle): { x: -1 | 0 | 1; y: -1 | 0 | 1 } {\n\tconst x = handle.includes('left') ? -1 : handle.includes('right') ? 1 : 0;\n\tconst y = handle.includes('top') ? -1 : handle.includes('bottom') ? 1 : 0;\n\treturn { x, y };\n}\n\n/**\n * The handle a resize from `start` to `next` was made with, from the edges that moved, or\n * `null` when no edge moved.\n */\nexport function inferResizeHandle(start: Rect, next: Rect, eps = 1e-6): ResizeHandle | null {\n\tconst moved = (a: number, b: number) => Math.abs(a - b) > eps;\n\tconst left = moved(start.x, next.x);\n\tconst right = moved(start.x + start.width, next.x + next.width);\n\tconst top = moved(start.y, next.y);\n\tconst bottom = moved(start.y + start.height, next.y + next.height);\n\tconst h = left ? 'left' : right ? 'right' : '';\n\tconst v = top ? 'top' : bottom ? 'bottom' : '';\n\tif (h && v) return `${v}-${h}` as ResizeHandle;\n\treturn (h || v || null) as ResizeHandle | null;\n}\n\nexport interface SnapResizeOptions extends Omit<SnapGuidesOptions, 'spacing'> {\n\t/**\n\t * Keep the input box's aspect ratio. A corner snaps whichever axis is closer to a target\n\t * and the other follows; a side scales the other axis about its centre.\n\t */\n\tkeepAspectRatio?: boolean;\n\t/** Smallest width a snap may leave (default: anything above 0). */\n\tminWidth?: number;\n\t/** Smallest height a snap may leave (default: anything above 0). */\n\tminHeight?: number;\n}\n\nexport interface SnapResizeResult {\n\t/** The snapped box. */\n\trect: Rect;\n\t/** Guide segments for the shape snaps that were applied (grid snaps draw none). */\n\tguides: GuideLine[];\n\t/** Whether the moving x edge snapped to a shape. */\n\tsnappedX: boolean;\n\t/** Whether the moving y edge snapped to a shape. */\n\tsnappedY: boolean;\n\t/** Whether `rect` differs from the input. */\n\tchanged: boolean;\n}\n\ninterface Hit {\n\tvalue: number;\n\tkind: 'edge' | 'centre' | 'grid';\n}\n\ntype Axis = GuideIndex['edgeX'];\n\nfunction within(axis: Axis, v: number, tolerance: number): number[] {\n\tconst { values } = axis;\n\tlet lo = 0;\n\tlet hi = values.length;\n\twhile (lo < hi) {\n\t\tconst mid = (lo + hi) >>> 1;\n\t\tif (values[mid] < v - tolerance) lo = mid + 1;\n\t\telse hi = mid;\n\t}\n\tconst out: number[] = [];\n\tfor (let i = lo; i < values.length && values[i] <= v + tolerance; i++) out.push(values[i]);\n\treturn out;\n}\n\nfunction matching(axis: Axis, v: number, eps: number): Rect[] {\n\tconst { values, rects } = axis;\n\tconst out: Rect[] = [];\n\tfor (let i = 0; i < values.length; i++) if (Math.abs(values[i] - v) <= eps) out.push(rects[i]);\n\treturn out;\n}\n\n/**\n * Snaps a resize of the axis-aligned box `rect` made with `handle` against `targets`. The\n * opposite edge (side handles) or corner (corner handles) stays fixed:\n *\n * - A **side** handle snaps its one moving edge to the nearest target edge or centre within\n * `threshold` screen pixels.\n * - A **corner** handle snaps x and y independently (unless `keepAspectRatio`).\n * - A snap that would collapse or flip the box (or go below `minWidth` / `minHeight`) is\n * rejected; the next nearest target is tried, else the edge stays where it was dragged.\n * - With `gridSize`, an axis without a shape snap snaps its moving edge to the grid.\n *\n * Returns the snapped box and the guides to draw (same shape as drag snapping).\n */\nexport function snapResize(\n\trect: Rect,\n\thandle: ResizeHandle,\n\ttargets: Rect[] | GuideIndex,\n\toptions: SnapResizeOptions = {}\n): SnapResizeResult {\n\tconst index = Array.isArray(targets) ? createGuideIndex(targets) : targets;\n\tconst zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;\n\tconst tolerance = (options.threshold ?? 6) / zoom;\n\tconst eps = 0.5 / zoom;\n\tconst useEdges = options.edges !== false;\n\tconst useCentres = options.centres !== false;\n\tconst grid = options.gridSize ?? 0;\n\tconst axes = resizeHandleAxes(handle);\n\tconst unchanged: SnapResizeResult = { rect: { ...rect }, guides: [], snappedX: false, snappedY: false, changed: false };\n\tif (![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite)) return unchanged;\n\n\tconst x0 = rect.x;\n\tconst x1 = rect.x + rect.width;\n\tconst y0 = rect.y;\n\tconst y1 = rect.y + rect.height;\n\n\t/** Best valid target for one moving edge, or `null`. */\n\tconst bestHit = (horizontal: boolean): Hit | null => {\n\t\tconst dir = horizontal ? axes.x : axes.y;\n\t\tif (dir === 0) return null;\n\t\tconst moving = horizontal ? (dir < 0 ? x0 : x1) : dir < 0 ? y0 : y1;\n\t\tconst fixed = horizontal ? (dir < 0 ? x1 : x0) : dir < 0 ? y1 : y0;\n\t\tconst min = (horizontal ? options.minWidth : options.minHeight) ?? 0;\n\t\tconst valid = (t: number) => {\n\t\t\tconst size = dir < 0 ? fixed - t : t - fixed;\n\t\t\treturn Number.isFinite(size) && size > 1e-9 && size >= min;\n\t\t};\n\t\tlet best: Hit | null = null;\n\t\tconst consider = (values: number[], kind: Hit['kind']) => {\n\t\t\tfor (const t of values) {\n\t\t\t\tif (!valid(t)) continue;\n\t\t\t\tif (!best || Math.abs(t - moving) < Math.abs(best.value - moving)) best = { value: t, kind };\n\t\t\t}\n\t\t};\n\t\tif (useEdges) consider(within(horizontal ? index.edgeX : index.edgeY, moving, tolerance), 'edge');\n\t\tif (useCentres) consider(within(horizontal ? index.centreX : index.centreY, moving, tolerance), 'centre');\n\t\tif (!best && grid > 0) {\n\t\t\tconst origin = horizontal ? options.gridOrigin?.x ?? 0 : options.gridOrigin?.y ?? 0;\n\t\t\tconsider([snapToGrid(moving, grid, origin)], 'grid');\n\t\t}\n\t\treturn best;\n\t};\n\n\tlet nx0 = x0;\n\tlet nx1 = x1;\n\tlet ny0 = y0;\n\tlet ny1 = y1;\n\tconst setEdge = (horizontal: boolean, value: number) => {\n\t\tconst dir = horizontal ? axes.x : axes.y;\n\t\tif (horizontal) {\n\t\t\tif (dir < 0) nx0 = value;\n\t\t\telse nx1 = value;\n\t\t} else if (dir < 0) ny0 = value;\n\t\telse ny1 = value;\n\t};\n\n\tlet hitX = bestHit(true);\n\tlet hitY = bestHit(false);\n\tconst ratio = rect.height > 0 ? rect.width / rect.height : 0;\n\n\tif (options.keepAspectRatio && ratio > 0 && (hitX || hitY)) {\n\t\tif (axes.x !== 0 && axes.y !== 0) {\n\t\t\t// Corner: the closer snap wins; the other axis follows from the fixed corner.\n\t\t\tconst dist = (h: Hit | null, horizontal: boolean) =>\n\t\t\t\th ? Math.abs(h.value - (horizontal ? (axes.x < 0 ? x0 : x1) : axes.y < 0 ? y0 : y1)) : Infinity;\n\t\t\tconst useX = dist(hitX, true) <= dist(hitY, false);\n\t\t\tif (useX && hitX) {\n\t\t\t\tsetEdge(true, hitX.value);\n\t\t\t\tconst height = (nx1 - nx0) / ratio;\n\t\t\t\tif (axes.y < 0) ny0 = ny1 - height;\n\t\t\t\telse ny1 = ny0 + height;\n\t\t\t\thitY = null;\n\t\t\t} else if (hitY) {\n\t\t\t\tsetEdge(false, hitY.value);\n\t\t\t\tconst width = (ny1 - ny0) * ratio;\n\t\t\t\tif (axes.x < 0) nx0 = nx1 - width;\n\t\t\t\telse nx1 = nx0 + width;\n\t\t\t\thitX = null;\n\t\t\t}\n\t\t\tconst min = (v: number | undefined) => v ?? 0;\n\t\t\tif (nx1 - nx0 < min(options.minWidth) || ny1 - ny0 < min(options.minHeight) || !(nx1 > nx0 && ny1 > ny0)) {\n\t\t\t\treturn unchanged;\n\t\t\t}\n\t\t} else if (axes.x !== 0 && hitX) {\n\t\t\tsetEdge(true, hitX.value);\n\t\t\tconst height = (nx1 - nx0) / ratio;\n\t\t\tconst cy = (y0 + y1) / 2;\n\t\t\tny0 = cy - height / 2;\n\t\t\tny1 = cy + height / 2;\n\t\t} else if (axes.y !== 0 && hitY) {\n\t\t\tsetEdge(false, hitY.value);\n\t\t\tconst width = (ny1 - ny0) * ratio;\n\t\t\tconst cx = (x0 + x1) / 2;\n\t\t\tnx0 = cx - width / 2;\n\t\t\tnx1 = cx + width / 2;\n\t\t}\n\t} else {\n\t\tif (hitX) setEdge(true, hitX.value);\n\t\tif (hitY) setEdge(false, hitY.value);\n\t}\n\n\tconst out: Rect = { x: nx0, y: ny0, width: nx1 - nx0, height: ny1 - ny0 };\n\tconst guides: GuideLine[] = [];\n\tconst guideFor = (horizontal: boolean, hit: Hit | null) => {\n\t\tif (!hit || hit.kind === 'grid') return;\n\t\tconst axis =\n\t\t\thit.kind === 'edge' ? (horizontal ? index.edgeX : index.edgeY) : horizontal ? index.centreX : index.centreY;\n\t\tconst aligned = matching(axis, hit.value, eps);\n\t\tconst span = boundsOf([out, ...aligned])!;\n\t\tguides.push(\n\t\t\thorizontal\n\t\t\t\t? { orientation: 'vertical', kind: hit.kind, x1: hit.value, y1: span.y, x2: hit.value, y2: span.y + span.height }\n\t\t\t\t: { orientation: 'horizontal', kind: hit.kind, x1: span.x, y1: hit.value, x2: span.x + span.width, y2: hit.value }\n\t\t);\n\t};\n\tguideFor(true, hitX);\n\tguideFor(false, hitY);\n\tconst changed = out.x !== rect.x || out.y !== rect.y || out.width !== rect.width || out.height !== rect.height;\n\treturn {\n\t\trect: out,\n\t\tguides,\n\t\tsnappedX: !!hitX && hitX.kind !== 'grid',\n\t\tsnappedY: !!hitY && hitY.kind !== 'grid',\n\t\tchanged\n\t};\n}\n\nexport interface SnapNodeResizeOptions extends SnapResizeOptions, NodeRectOptions {\n\t/** Index from `createNodeGuideIndex(nodes, [id])`, reused across resize frames. */\n\tindex?: GuideIndex;\n}\n\nexport interface SnapNodeResizeResult extends SnapResizeResult {\n\t/** Position / size update for the node (parent-relative position), or none. */\n\tupdates: CanvasNodeUpdate[];\n}\n\n/**\n * Snaps a node that is being resized. `rect` is the node's proposed absolute box (e.g. from\n * a resize event) and `handle` the handle being dragged. Locked nodes are never snapped.\n * The node itself and its descendants are not snap targets.\n */\nexport function snapNodeResize(\n\tnodes: CanvasNode[],\n\tid: string,\n\trect: Rect,\n\thandle: ResizeHandle,\n\toptions: SnapNodeResizeOptions = {}\n): SnapNodeResizeResult {\n\tconst lookup = createNodeLookup(nodes);\n\tconst node = lookup.get(id);\n\tif (!node || isLocked(node)) {\n\t\treturn { rect: { ...rect }, guides: [], snappedX: false, snappedY: false, changed: false, updates: [] };\n\t}\n\tconst index = options.index ?? createNodeGuideIndex(nodes, [id], options);\n\tconst result = snapResize(rect, handle, index, options);\n\tif (!result.changed) return { ...result, updates: [] };\n\tconst parent = node.parentId ? lookup.get(node.parentId) : undefined;\n\tconst origin = parent ? getAbsolutePosition(parent, lookup) : { x: 0, y: 0 };\n\treturn {\n\t\t...result,\n\t\tupdates: [\n\t\t\t{\n\t\t\t\tid,\n\t\t\t\tposition: { x: result.rect.x - origin.x, y: result.rect.y - origin.y },\n\t\t\t\twidth: result.rect.width,\n\t\t\t\theight: result.rect.height\n\t\t\t}\n\t\t]\n\t};\n}\n\n/**\n * Absolute box of a node while it is being resized: flow libraries write the new size to\n * `width` / `height` before the DOM is re-measured, so those win over `measured` here.\n */\nexport function getResizingNodeRect(\n\tnode: CanvasNode,\n\tlookup: ReadonlyMap<string, CanvasNode>,\n\toptions: NodeRectOptions = {}\n): Rect {\n\tconst { x, y } = node.parentId ? getAbsolutePosition(node, lookup) : node.position;\n\tconst size = getNodeSize(node, options);\n\treturn { x, y, width: node.width ?? size.width, height: node.height ?? size.height };\n}\n","// Portions ported from figcraft/crates/layout/src/lib.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport { createNodeLookup, getAbsolutePosition, getNodeSize, type CanvasNodeUpdate, type NodeRectOptions } from './arrange.js';\nimport type { GuideLine } from './guides.js';\nimport type { CanvasNode, XYPosition } from './types.js';\n\n/**\n * Figma-style auto layout for xyflow sub-flows.\n *\n * A *frame* is any node with `data.layout` (an {@link AutoLayout}); its children are the nodes\n * whose `parentId` is the frame, in node-array order. Children may carry `data.childLayout`\n * (an {@link AutoLayoutChild}) for their own sizing, min / max, absolute positioning,\n * constraints and grid cell. The functions here are pure: they return `CanvasNodeUpdate`s\n * (positions relative to the parent, sizes) for `applyNodeUpdates`.\n */\n\n/** Data key holding a frame's {@link AutoLayout}. */\nexport const AUTO_LAYOUT_DATA_KEY = 'layout';\n/** Data key holding a child's {@link AutoLayoutChild}. */\nexport const CHILD_LAYOUT_DATA_KEY = 'childLayout';\n\nexport type AutoLayoutMode = 'horizontal' | 'vertical' | 'grid';\n/** `fixed` keeps the size, `hug` fits the content (frames and text), `fill` shares the parent's free space. */\nexport type AutoLayoutSizing = 'fixed' | 'hug' | 'fill';\nexport type AutoLayoutAlignMain = 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly';\nexport type AutoLayoutAlignCross = 'start' | 'center' | 'end' | 'stretch' | 'baseline';\n/** How wrapped lines share the free cross space: packed by `alignCross` (`auto`) or spread (`space-between`). */\nexport type AutoLayoutAlignContent = 'auto' | 'space-between';\n/** `number` (all sides), `[vertical, horizontal]`, `[top, right, bottom, left]` or per side. */\nexport type AutoLayoutPadding =\n\t| number\n\t| [number, number]\n\t| [number, number, number, number]\n\t| { top?: number; right?: number; bottom?: number; left?: number };\n\nexport interface AutoLayoutSize {\n\twidth?: number;\n\theight?: number;\n}\n\nexport interface AutoLayoutSizingPair {\n\twidth?: AutoLayoutSizing;\n\theight?: AutoLayoutSizing;\n}\n\n/** A grid track: pixels, `'hug'` (fits its content), `'2fr'` or `{ fr: 2 }` (shares the rest like CSS `fr`). */\nexport type AutoLayoutGridTrack = number | 'hug' | `${number}fr` | { fr: number };\n\nexport interface AutoLayoutGrid {\n\t/** Column count (all `1fr`) or the column tracks (default 2 columns). */\n\tcolumns?: number | AutoLayoutGridTrack[];\n\t/** Minimum row count or the row tracks (default 1); rows are added to fit the children. Missing tracks are `1fr`. */\n\trows?: number | AutoLayoutGridTrack[];\n}\n\n/** A frame's auto layout (`node.data.layout`). */\nexport interface AutoLayout {\n\tmode: AutoLayoutMode;\n\t/** Main-axis gap (grid: column gap). Negative gaps overlap. Default 0. */\n\tgap?: number;\n\t/** Gap between wrapped lines (grid: row gap). Default: `gap`. */\n\trowGap?: number;\n\t/** Default 0. */\n\tpadding?: AutoLayoutPadding;\n\t/** Horizontal / vertical: wrap into lines when the main axis runs out (ignored while hugging it). */\n\twrap?: boolean;\n\t/** Default `start`. Grid: the cell alignment along x (`start`, `center`, `end`). */\n\talignMain?: AutoLayoutAlignMain;\n\t/** Default `start`. `baseline` applies to horizontal layouts. Grid: the cell alignment along y. */\n\talignCross?: AutoLayoutAlignCross;\n\talignContent?: AutoLayoutAlignContent;\n\t/** The frame's own sizing (`childLayout.sizing` wins when both are set). Default `fixed`. */\n\tsizing?: AutoLayoutSizingPair;\n\t/** The frame's own limits (`childLayout.min` / `max` win when set). */\n\tmin?: AutoLayoutSize;\n\tmax?: AutoLayoutSize;\n\tgrid?: AutoLayoutGrid;\n}\n\nexport type AutoLayoutHorizontalConstraint = 'left' | 'right' | 'leftRight' | 'center' | 'scale';\nexport type AutoLayoutVerticalConstraint = 'top' | 'bottom' | 'topBottom' | 'center' | 'scale';\n\nexport interface AutoLayoutConstraints {\n\thorizontal?: AutoLayoutHorizontalConstraint;\n\tvertical?: AutoLayoutVerticalConstraint;\n}\n\nexport type AutoLayoutCellAlign = 'start' | 'center' | 'end';\n\n/** A grid child's cell. Column / row are 0-based anchors; unanchored children flow in reading order. */\nexport interface AutoLayoutGridArea {\n\tcolumn?: number;\n\trow?: number;\n\tcolumnSpan?: number;\n\trowSpan?: number;\n\t/** The child's own alignment in its cell (wins over the frame's). */\n\talignX?: AutoLayoutCellAlign;\n\talignY?: AutoLayoutCellAlign;\n}\n\n/** A child's layout properties (`node.data.childLayout`). */\nexport interface AutoLayoutChild {\n\tsizing?: AutoLayoutSizingPair;\n\tmin?: AutoLayoutSize;\n\tmax?: AutoLayoutSize;\n\t/** Ignore auto layout: keep its place and follow `constraints` when the frame resizes. */\n\tabsolute?: boolean;\n\t/** How the child follows its parent's resizes (default left / top). */\n\tconstraints?: AutoLayoutConstraints;\n\tgridArea?: AutoLayoutGridArea;\n}\n\n/**\n * Measuring hooks. `size` defaults to `getNodeSize` (xyflow `measured` size, then `width` /\n * `height`, then style). Nodes for which `textSize` returns a size are *text-like*: they can\n * hug, and wrap (`textHeight`) when their width is set by the layout.\n */\nexport interface AutoLayoutMeasure {\n\tsize?: (node: CanvasNode) => { width: number; height: number } | undefined;\n\t/** Natural (unwrapped) size of a text-like node. */\n\ttextSize?: (node: CanvasNode) => { width: number; height: number } | undefined;\n\t/** Height of a text-like node wrapped at `width`. */\n\ttextHeight?: (node: CanvasNode, width: number) => number | undefined;\n\t/** Distance from the node's top to its first text baseline (for `alignCross: 'baseline'`). */\n\ttextBaseline?: (node: CanvasNode) => number | undefined;\n}\n\nexport interface AutoLayoutOptions {\n\tmeasure?: AutoLayoutMeasure;\n\t/** Size resolution for unmeasured nodes when `measure.size` is not given. */\n\trectOptions?: NodeRectOptions;\n}\n\ntype Axis = 'width' | 'height';\n\nconst MODES = new Set(['horizontal', 'vertical', 'grid']);\nconst EPS = 1e-6;\nconst MAX_TRACKS = 1000;\nconst MAX_GRID_CELLS = 1_000_000;\n\n/** Clamps a size to `[min, max]`; non-finite or negative sizes become 0 and min wins over a smaller max. */\nexport function clampLayoutSize(value: number, min?: number, max?: number): number {\n\tlet v = Number.isFinite(value) ? Math.max(value, 0) : 0;\n\tif (typeof max === 'number' && Number.isFinite(max)) v = Math.min(v, max);\n\tif (typeof min === 'number' && Number.isFinite(min)) v = Math.max(v, min);\n\treturn v;\n}\n\nconst finite = (v: unknown): number => (typeof v === 'number' && Number.isFinite(v) ? v : 0);\n\n/** The node's auto layout, or `undefined` when it is not an auto layout frame. */\nexport function getAutoLayout(node: CanvasNode | undefined | null): AutoLayout | undefined {\n\tconst layout = node?.data?.[AUTO_LAYOUT_DATA_KEY];\n\treturn layout && typeof layout === 'object' && MODES.has(layout.mode) ? (layout as AutoLayout) : undefined;\n}\n\n/** Whether the node is an auto layout frame. */\nexport function isAutoLayoutFrame(node: CanvasNode | undefined | null): boolean {\n\treturn getAutoLayout(node) !== undefined;\n}\n\n/** The node's child layout properties (empty when unset). */\nexport function getChildLayout(node: CanvasNode | undefined | null): AutoLayoutChild {\n\tconst child = node?.data?.[CHILD_LAYOUT_DATA_KEY];\n\treturn child && typeof child === 'object' ? (child as AutoLayoutChild) : {};\n}\n\n/** Effective sizing of a node on one axis: `childLayout.sizing`, then `layout.sizing`, then `fixed`. */\nexport function getLayoutSizing(node: CanvasNode, axis: Axis): AutoLayoutSizing {\n\tconst value = getChildLayout(node).sizing?.[axis] ?? getAutoLayout(node)?.sizing?.[axis];\n\treturn value === 'hug' || value === 'fill' ? value : 'fixed';\n}\n\nfunction limitsOf(node: CanvasNode) {\n\tconst child = getChildLayout(node);\n\tconst layout = getAutoLayout(node);\n\tconst min = child.min ?? layout?.min ?? {};\n\tconst max = child.max ?? layout?.max ?? {};\n\treturn { minW: min.width, maxW: max.width, minH: min.height, maxH: max.height };\n}\n\n/** Padding as `[top, right, bottom, left]`, sanitised (non-finite and negative values become 0). */\nexport function resolvePadding(padding: AutoLayoutPadding | undefined): [number, number, number, number] {\n\tlet sides: [unknown, unknown, unknown, unknown];\n\tif (typeof padding === 'number') sides = [padding, padding, padding, padding];\n\telse if (Array.isArray(padding)) {\n\t\tsides = padding.length === 2 ? [padding[0], padding[1], padding[0], padding[1]] : [padding[0], padding[1], padding[2], padding[3]];\n\t} else if (padding && typeof padding === 'object') sides = [padding.top, padding.right, padding.bottom, padding.left];\n\telse sides = [0, 0, 0, 0];\n\treturn sides.map((p) => Math.max(finite(p), 0)) as [number, number, number, number];\n}\n\ninterface Box {\n\tid: string;\n\tnode: CanvasNode;\n\tparent?: string;\n\tx: number;\n\ty: number;\n\tw: number;\n\th: number;\n\tkids: string[];\n}\n\n/** Working copy of the node geometry the layout passes mutate. */\nclass LayoutState {\n\treadonly boxes = new Map<string, Box>();\n\treadonly measure: AutoLayoutMeasure;\n\tprivate readonly visiting = new Set<string>();\n\tprivate readonly original = new Map<string, { x: number; y: number; w: number; h: number }>();\n\n\tconstructor(nodes: CanvasNode[], options: AutoLayoutOptions = {}) {\n\t\tthis.measure = options.measure ?? {};\n\t\tfor (const node of nodes) {\n\t\t\tconst size = this.measure.size?.(node) ?? getNodeSize(node, options.rectOptions);\n\t\t\tconst box: Box = {\n\t\t\t\tid: node.id,\n\t\t\t\tnode,\n\t\t\t\tparent: node.parentId,\n\t\t\t\tx: finite(node.position?.x),\n\t\t\t\ty: finite(node.position?.y),\n\t\t\t\tw: finite(size.width),\n\t\t\t\th: finite(size.height),\n\t\t\t\tkids: []\n\t\t\t};\n\t\t\tthis.boxes.set(node.id, box);\n\t\t\tthis.original.set(node.id, { x: box.x, y: box.y, w: box.w, h: box.h });\n\t\t}\n\t\tfor (const node of nodes) {\n\t\t\tif (node.parentId && node.parentId !== node.id) this.boxes.get(node.parentId)?.kids.push(node.id);\n\t\t}\n\t}\n\n\tupdates(): CanvasNodeUpdate[] {\n\t\tconst out: CanvasNodeUpdate[] = [];\n\t\tfor (const [id, box] of this.boxes) {\n\t\t\tconst o = this.original.get(id)!;\n\t\t\tconst update: CanvasNodeUpdate = { id };\n\t\t\tif (Math.abs(box.x - o.x) > EPS || Math.abs(box.y - o.y) > EPS) update.position = { x: box.x, y: box.y };\n\t\t\tif (Math.abs(box.w - o.w) > EPS) update.width = box.w;\n\t\t\tif (Math.abs(box.h - o.h) > EPS) update.height = box.h;\n\t\t\tif (update.position || update.width !== undefined || update.height !== undefined) out.push(update);\n\t\t}\n\t\treturn out;\n\t}\n\n\t/** Runs `fn` for `id` unless it is already on the stack (guards `parentId` cycles). */\n\tguard(id: string, fn: () => void) {\n\t\tif (this.visiting.has(id)) return;\n\t\tthis.visiting.add(id);\n\t\ttry {\n\t\t\tfn();\n\t\t} finally {\n\t\t\tthis.visiting.delete(id);\n\t\t}\n\t}\n\n\tancestors(id: string): string[] {\n\t\tconst out: string[] = [];\n\t\tconst seen = new Set([id]);\n\t\tlet parent = this.boxes.get(id)?.parent;\n\t\twhile (parent && !seen.has(parent) && this.boxes.has(parent)) {\n\t\t\tout.push(parent);\n\t\t\tseen.add(parent);\n\t\t\tparent = this.boxes.get(parent)!.parent;\n\t\t}\n\t\treturn out;\n\t}\n}\n\nfunction setSize(s: LayoutState, id: string, w: number, h: number) {\n\tconst b = s.boxes.get(id);\n\tif (!b) return;\n\tb.w = Number.isFinite(w) ? Math.max(w, 0) : b.w;\n\tb.h = Number.isFinite(h) ? Math.max(h, 0) : b.h;\n}\n\nfunction place(s: LayoutState, id: string, x: number, y: number) {\n\tconst b = s.boxes.get(id);\n\tif (!b) return;\n\tb.x = x;\n\tb.y = y;\n}\n\nconst isText = (s: LayoutState, b: Box) => !!s.measure.textSize && s.measure.textSize(b.node) !== undefined;\n\n/** The children an auto layout frame places: visible and not absolute. */\nfunction flowChildren(s: LayoutState, frame: string): string[] {\n\tconst f = s.boxes.get(frame);\n\tif (!f) return [];\n\treturn f.kids.filter((k) => {\n\t\tconst n = s.boxes.get(k)!.node;\n\t\treturn n.hidden !== true && !getChildLayout(n).absolute;\n\t});\n}\n\n/** Text that hugs its width refreshes its natural size (wrapping at its max width); a hug-height text gets its wrapped height. */\nfunction fitHugText(s: LayoutState, id: string) {\n\tconst b = s.boxes.get(id);\n\tif (!b || !isText(s, b)) return;\n\tconst natural = s.measure.textSize!(b.node)!;\n\tconst { minW, maxW } = limitsOf(b.node);\n\tif (getLayoutSizing(b.node, 'width') === 'hug') {\n\t\tconst w = clampLayoutSize(natural.width, minW, maxW);\n\t\tconst limited = Math.abs(w - natural.width) > EPS;\n\t\tconst h = limited ? s.measure.textHeight?.(b.node, w) ?? natural.height : natural.height;\n\t\tsetSize(s, id, w, h);\n\t} else if (getLayoutSizing(b.node, 'height') === 'hug') {\n\t\tsetSize(s, id, b.w, s.measure.textHeight?.(b.node, b.w) ?? b.h);\n\t}\n}\n\n/** Clamps an in-flow child's own size to its min / max. */\nfunction enforceLimits(s: LayoutState, id: string) {\n\tconst b = s.boxes.get(id);\n\tif (!b) return;\n\tconst { minW, maxW, minH, maxH } = limitsOf(b.node);\n\tif (minW === undefined && maxW === undefined && minH === undefined && maxH === undefined) return;\n\tconst nw = clampLayoutSize(b.w, minW, maxW);\n\tconst nh = clampLayoutSize(b.h, minH, maxH);\n\tif (Math.abs(nw - b.w) < 1e-9 && Math.abs(nh - b.h) < 1e-9) return;\n\tif (isText(s, b)) setSize(s, id, nw, nh);\n\telse resizeChild(s, id, nw, nh);\n}\n\nfunction itemBaseline(s: LayoutState, id: string, depth: number): number | undefined {\n\tconst b = s.boxes.get(id);\n\tif (!b) return undefined;\n\tif (isText(s, b)) {\n\t\tconst v = s.measure.textBaseline?.(b.node);\n\t\treturn typeof v === 'number' && Number.isFinite(v) ? v : undefined;\n\t}\n\tconst layout = getAutoLayout(b.node);\n\tif (!layout || layout.mode === 'grid' || depth >= 32) return undefined;\n\tfor (const k of flowChildren(s, id)) {\n\t\tconst base = itemBaseline(s, k, depth + 1);\n\t\tif (base !== undefined) return s.boxes.get(k)!.y + base;\n\t}\n\treturn undefined;\n}\n\n/** Resizes a child; text with hugging height re-wraps, frames apply constraints and re-run their layout. */\nfunction resizeChild(s: LayoutState, id: string, w: number, h: number) {\n\tconst b = s.boxes.get(id);\n\tif (!b) return;\n\tif (isText(s, b) && getLayoutSizing(b.node, 'height') === 'hug') {\n\t\tsetSize(s, id, w, s.measure.textHeight?.(b.node, w) ?? h);\n\t\treturn;\n\t}\n\tconst ow = b.w;\n\tconst oh = b.h;\n\tsetSize(s, id, w, h);\n\tif (b.kids.length > 0) constrain(s, id, ow, oh);\n\tif (getAutoLayout(b.node)) layout(s, id);\n}\n\ninterface Item {\n\tid: string;\n\tmain: number;\n\tcross: number;\n\tfillMain: boolean;\n\tfillCross: boolean;\n\tminMain?: number;\n\tmaxMain?: number;\n\tminCross?: number;\n\tmaxCross?: number;\n\tbaseline?: number;\n\tbaseOff: number;\n}\n\n/** Where the first child starts and the step between children along the main axis. */\nfunction mainOffsets(align: AutoLayoutAlignMain, inner: number, total: number, n: number, gap: number): [number, number] {\n\tconst gaps = gap * Math.max(n - 1, 0);\n\tconst free = inner - total;\n\tconst k = Math.max(n, 1);\n\tswitch (align) {\n\t\tcase 'center':\n\t\t\treturn [(free - gaps) / 2, gap];\n\t\tcase 'end':\n\t\t\treturn [free - gaps, gap];\n\t\tcase 'space-between':\n\t\t\treturn [0, n > 1 ? Math.max(free / (n - 1), 0) : 0];\n\t\tcase 'space-around': {\n\t\t\tconst g = Math.max(free / k, 0);\n\t\t\treturn [g / 2, g];\n\t\t}\n\t\tcase 'space-evenly': {\n\t\t\tconst g = Math.max(free / (k + 1), 0);\n\t\t\treturn [g, g];\n\t\t}\n\t\tdefault:\n\t\t\treturn [0, gap];\n\t}\n}\n\n/**\n * Shares `free` main-axis space among the fill items like CSS flexbox: equal shares within\n * min / max; when clamping adds more than it removes, the items held at their min keep it\n * (otherwise those held at their max do) and the rest share what is left again.\n */\nfunction distribute(items: Item[], open: number[], free: number) {\n\tfree = Math.max(free, 0);\n\twhile (open.length > 0) {\n\t\tconst share = free / open.length;\n\t\tconst sized = open.map((i) => [i, clampLayoutSize(share, items[i].minMain, items[i].maxMain)] as const);\n\t\tconst violation = sized.reduce((sum, [, v]) => sum + (v - share), 0);\n\t\tconst frozen = (v: number) =>\n\t\t\tMath.abs(violation) < 1e-9 || (violation > 0 && v > share + 1e-9) || (violation < 0 && v < share - 1e-9);\n\t\tfor (const [i, v] of sized) {\n\t\t\tif (frozen(v)) {\n\t\t\t\titems[i].main = v;\n\t\t\t\tfree -= v;\n\t\t\t}\n\t\t}\n\t\tfree = Math.max(free, 0);\n\t\topen = sized.filter(([, v]) => !frozen(v)).map(([i]) => i);\n\t}\n}\n\nfunction layout(s: LayoutState, frame: string) {\n\ts.guard(frame, () => layoutFrameInner(s, frame));\n}\n\nfunction layoutFrameInner(s: LayoutState, frame: string) {\n\tconst f = s.boxes.get(frame);\n\tconst al = f && getAutoLayout(f.node);\n\tif (!f || !al) return;\n\tconst horizontal = al.mode !== 'vertical';\n\tconst hugW = getLayoutSizing(f.node, 'width') === 'hug';\n\tconst hugH = getLayoutSizing(f.node, 'height') === 'hug';\n\tconst ow = f.w;\n\tconst oh = f.h;\n\tconst fl = limitsOf(f.node);\n\tconst [pt, pr, pb, pl] = resolvePadding(al.padding);\n\tconst fw = Math.max(clampLayoutSize(ow, fl.minW, fl.maxW), pl + pr);\n\tconst fh = Math.max(clampLayoutSize(oh, fl.minH, fl.maxH), pt + pb);\n\tconst kids = flowChildren(s, frame);\n\n\tfor (const c of kids) {\n\t\tfitHugText(s, c);\n\t\tenforceLimits(s, c);\n\t}\n\n\tif (al.mode === 'grid') {\n\t\tlayoutGrid(s, frame, al);\n\t\treturn;\n\t}\n\n\tconst innerMainFixed = horizontal ? fw - pl - pr : fh - pt - pb;\n\tconst innerCrossFixed = horizontal ? fh - pt - pb : fw - pl - pr;\n\tconst hugMain = horizontal ? hugW : hugH;\n\tconst hugCross = horizontal ? hugH : hugW;\n\tconst alignCross = al.alignCross ?? 'start';\n\tconst baseline = horizontal && alignCross === 'baseline';\n\tconst stretch = alignCross === 'stretch';\n\n\tconst items: Item[] = [];\n\tfor (const c of kids) {\n\t\tconst b = s.boxes.get(c)!;\n\t\tconst lim = limitsOf(b.node);\n\t\tconst sm = getLayoutSizing(b.node, horizontal ? 'width' : 'height');\n\t\tconst sc = getLayoutSizing(b.node, horizontal ? 'height' : 'width');\n\t\titems.push({\n\t\t\tid: c,\n\t\t\tmain: horizontal ? b.w : b.h,\n\t\t\tcross: horizontal ? b.h : b.w,\n\t\t\t// Fill needs room to fill: on an axis the frame hugs it reads as fixed.\n\t\t\tfillMain: sm === 'fill' && !hugMain,\n\t\t\tfillCross: (sc === 'fill' || (stretch && sc !== 'hug')) && !hugCross,\n\t\t\tminMain: horizontal ? lim.minW : lim.minH,\n\t\t\tmaxMain: horizontal ? lim.maxW : lim.maxH,\n\t\t\tminCross: horizontal ? lim.minH : lim.minW,\n\t\t\tmaxCross: horizontal ? lim.maxH : lim.maxW,\n\t\t\tbaseOff: 0\n\t\t});\n\t}\n\n\tconst gap = finite(al.gap);\n\tconst alignMain = al.alignMain ?? 'start';\n\tconst autoGap = alignMain === 'space-between' || alignMain === 'space-around' || alignMain === 'space-evenly';\n\tconst wraps = !!al.wrap && !hugMain;\n\tconst sizeOf = (id: string) => {\n\t\tconst b = s.boxes.get(id)!;\n\t\treturn horizontal ? [b.w, b.h] : [b.h, b.w];\n\t};\n\tconst resize = (it: Item, main: number, cross: number) =>\n\t\tresizeChild(s, it.id, horizontal ? main : cross, horizontal ? cross : main);\n\n\t// Fill across a single line: resize those children first so their main size is final.\n\tif (!wraps) {\n\t\tfor (const it of items) {\n\t\t\tif (!it.fillCross || it.fillMain) continue;\n\t\t\tresize(it, it.main, clampLayoutSize(innerCrossFixed, it.minCross, it.maxCross));\n\t\t\tit.main = sizeOf(it.id)[0];\n\t\t}\n\t}\n\n\t// Split into lines along the main axis.\n\tconst lines: number[][] = [];\n\tif (wraps) {\n\t\tlet cur: number[] = [];\n\t\tlet used = 0;\n\t\titems.forEach((it, i) => {\n\t\t\tconst sz = it.fillMain ? it.minMain ?? 0 : it.main;\n\t\t\tconst add = cur.length === 0 ? sz : Math.max(gap, 0) + sz;\n\t\t\tif (cur.length > 0 && used + add > innerMainFixed + 1e-6) {\n\t\t\t\tlines.push(cur);\n\t\t\t\tcur = [];\n\t\t\t\tused = sz;\n\t\t\t} else {\n\t\t\t\tused += add;\n\t\t\t}\n\t\t\tcur.push(i);\n\t\t});\n\t\tif (cur.length > 0) lines.push(cur);\n\t} else {\n\t\tlines.push(items.map((_, i) => i));\n\t}\n\n\t// Share each line's free space among its fill children.\n\tfor (const line of lines) {\n\t\tconst fixed = line.filter((i) => !items[i].fillMain).reduce((sum, i) => sum + items[i].main, 0);\n\t\tconst gaps = gap * Math.max(line.length - 1, 0);\n\t\tconst fills = line.filter((i) => items[i].fillMain);\n\t\tif (fills.length > 0) distribute(items, fills, innerMainFixed - fixed - (autoGap ? 0 : gaps));\n\t}\n\tfor (const it of items) {\n\t\tif (!it.fillMain) continue;\n\t\tconst cross = it.fillCross && !wraps ? clampLayoutSize(innerCrossFixed, it.minCross, it.maxCross) : it.cross;\n\t\tresize(it, it.main, cross);\n\t\tit.cross = sizeOf(it.id)[1];\n\t}\n\tif (baseline) {\n\t\tfor (const it of items) if (!it.fillCross) it.baseline = itemBaseline(s, it.id, 0);\n\t}\n\n\t// Line extents: [main used, cross size].\n\tconst extents: [number, number][] = [];\n\tfor (const line of lines) {\n\t\tconst gaps = gap * Math.max(line.length - 1, 0);\n\t\tconst lineBase = line.reduce((m, i) => Math.max(m, items[i].baseline ?? 0), 0);\n\t\tfor (const i of line) items[i].baseOff = items[i].baseline !== undefined ? lineBase - items[i].baseline! : 0;\n\t\tconst inLine = line.map((i) => items[i]);\n\t\tconst mainUsed = inLine.reduce((sum, it) => sum + it.main, 0) + gaps;\n\t\t// A line of nothing but fill-across children keeps their own size.\n\t\tconst allFill = inLine.every((it) => it.fillCross);\n\t\tconst cross = inLine.filter((it) => allFill || !it.fillCross).reduce((m, it) => Math.max(m, it.baseOff + it.cross), 0);\n\t\textents.push([mainUsed, cross]);\n\t}\n\n\t// Container size.\n\tconst contentMain = extents.reduce((m, l) => Math.max(m, l[0]), 0);\n\tlet crossGap = al.rowGap !== undefined ? finite(al.rowGap) : gap;\n\tconst contentCross = extents.reduce((sum, l) => sum + l[1], 0) + crossGap * Math.max(lines.length - 1, 0);\n\tlet nw = fw;\n\tlet nh = fh;\n\tif (hugMain) {\n\t\tconst v = contentMain + (horizontal ? pl + pr : pt + pb);\n\t\tif (horizontal) nw = v;\n\t\telse nh = v;\n\t}\n\tif (hugCross) {\n\t\tconst v = contentCross + (horizontal ? pt + pb : pl + pr);\n\t\tif (horizontal) nh = v;\n\t\telse nw = v;\n\t}\n\tnw = Math.max(clampLayoutSize(nw, fl.minW, fl.maxW), pl + pr);\n\tnh = Math.max(clampLayoutSize(nh, fl.minH, fl.maxH), pt + pb);\n\tsetSize(s, frame, nw, nh);\n\tconstrain(s, frame, ow, oh);\n\tconst innerMain = horizontal ? nw - pl - pr : nh - pt - pb;\n\tconst innerCross = horizontal ? nh - pt - pb : nw - pl - pr;\n\n\t// Position. Several lines share the free space across the flow.\n\tlet crossPos = horizontal ? pt : pl;\n\tconst single = lines.length === 1;\n\tif (lines.length > 1) {\n\t\tconst linesCross = extents.reduce((sum, l) => sum + l[1], 0);\n\t\tconst between = lines.length - 1;\n\t\tconst spread = (innerCross - linesCross) / between;\n\t\tconst spaceBetween = al.alignContent === 'space-between';\n\t\tif (spaceBetween && spread > 0) {\n\t\t\tcrossGap = spread;\n\t\t} else {\n\t\t\tif (spaceBetween) crossGap = 0;\n\t\t\tconst free = innerCross - linesCross - crossGap * between;\n\t\t\tif (free > 0 && items.every((it) => it.fillCross)) {\n\t\t\t\tconst extra = free / lines.length;\n\t\t\t\tfor (const l of extents) l[1] += extra;\n\t\t\t} else if (Number.isFinite(free)) {\n\t\t\t\tcrossPos += alignCross === 'center' ? free / 2 : alignCross === 'end' ? free : 0;\n\t\t\t}\n\t\t}\n\t}\n\tlines.forEach((line, li) => {\n\t\tconst [used, ownCross] = extents[li];\n\t\tconst lineCross = single ? innerCross : ownCross;\n\t\tconst total = used - gap * Math.max(line.length - 1, 0);\n\t\tlet [pos, step] = mainOffsets(alignMain, innerMain, total, line.length, gap);\n\t\tpos += horizontal ? pl : pt;\n\t\tfor (const i of line) {\n\t\t\tconst it = items[i];\n\t\t\tconst crossSize = it.fillCross ? clampLayoutSize(lineCross, it.minCross, it.maxCross) : it.cross;\n\t\t\tlet off = 0;\n\t\t\tif (alignCross === 'baseline') off = it.baseOff;\n\t\t\telse if (alignCross === 'center') off = (lineCross - crossSize) / 2;\n\t\t\telse if (alignCross === 'end') off = lineCross - crossSize;\n\t\t\t// Fill across a wrapped line: its size is known only now.\n\t\t\tif (it.fillCross && wraps) resize(it, it.main, crossSize);\n\t\t\tconst x = horizontal ? pos : crossPos + off;\n\t\t\tconst y = horizontal ? crossPos + off : pos;\n\t\t\tplace(s, it.id, finite(x), finite(y));\n\t\t\tpos += it.main + step;\n\t\t}\n\t\tcrossPos += lineCross + crossGap;\n\t});\n}\n\n// ---------------------------------------------------------------------------------------------\n// Grid\n\ninterface TrackSpec {\n\tsizing: AutoLayoutSizing;\n\tvalue: number;\n}\n\nfunction parseTrack(track: AutoLayoutGridTrack | undefined): TrackSpec {\n\tif (typeof track === 'number') return { sizing: 'fixed', value: track };\n\tif (track === 'hug') return { sizing: 'hug', value: 0 };\n\tif (typeof track === 'string') {\n\t\tconst match = /^\\s*(-?\\d*\\.?\\d+)\\s*fr\\s*$/i.exec(track);\n\t\tif (match) return { sizing: 'fill', value: Number(match[1]) };\n\t}\n\tif (track && typeof track === 'object' && 'fr' in track) return { sizing: 'fill', value: track.fr };\n\treturn { sizing: 'fill', value: 1 };\n}\n\nfunction trackList(spec: number | AutoLayoutGridTrack[] | undefined, fallback: number): { count: number; tracks: TrackSpec[] } {\n\tif (Array.isArray(spec)) return { count: Math.min(Math.max(spec.length, 1), MAX_TRACKS), tracks: spec.map(parseTrack) };\n\tconst n = typeof spec === 'number' && Number.isFinite(spec) ? Math.floor(spec) : fallback;\n\treturn { count: Math.min(Math.max(n, 1), MAX_TRACKS), tracks: [] };\n}\n\nfunction trackSpec(tracks: TrackSpec[], i: number): TrackSpec {\n\tconst t = tracks[i] ?? { sizing: 'fill', value: 1 };\n\tconst value = Number.isFinite(t.value) ? Math.min(Math.max(t.value, 0), 1e6) : 0;\n\treturn { sizing: t.sizing, value };\n}\n\n/** A grid child's cell (0-based column / row and spans). */\nexport interface AutoLayoutGridCell {\n\tid: string;\n\tcolumn: number;\n\trow: number;\n\tcolumnSpan: number;\n\trowSpan: number;\n}\n\n/** A grid frame's resolved geometry (frame-local): column x ranges, row y ranges, cells and the size it wants. */\nexport interface AutoLayoutGridGeometry {\n\tcolumns: [number, number][];\n\trows: [number, number][];\n\tcells: AutoLayoutGridCell[];\n\twidth: number;\n\theight: number;\n}\n\nfunction cellRect(g: AutoLayoutGridGeometry, c: AutoLayoutGridCell) {\n\tconst x0 = g.columns[c.column]?.[0] ?? 0;\n\tconst x1 = g.columns[c.column + c.columnSpan - 1]?.[1] ?? x0;\n\tconst y0 = g.rows[c.row]?.[0] ?? 0;\n\tconst y1 = g.rows[c.row + c.rowSpan - 1]?.[1] ?? y0;\n\treturn { x: x0, y: y0, width: Math.max(x1, x0) - x0, height: Math.max(y1, y0) - y0 };\n}\n\nconst toIndex = (v: unknown): number | undefined =>\n\ttypeof v === 'number' && Number.isFinite(v) && v >= 0 ? Math.floor(v) : undefined;\n\n/** Places grid children: anchored ones first, then the rest in reading order into the next free cells. */\nfunction gridPlace(s: LayoutState, al: AutoLayout, kids: string[]): { cells: AutoLayoutGridCell[]; rows: number } {\n\tconst cols = trackList(al.grid?.columns, 2).count;\n\tconst minRows = trackList(al.grid?.rows, 1).count;\n\tconst maxRows = Math.min(Math.max(Math.floor(MAX_GRID_CELLS / cols), 1), MAX_TRACKS);\n\tconst occ: boolean[][] = [];\n\tconst free = (c: number, r: number, cs: number, rs: number) => {\n\t\tfor (let ri = r; ri < r + rs; ri++) {\n\t\t\tconst row = occ[ri];\n\t\t\tif (!row) continue;\n\t\t\tfor (let ci = c; ci < c + cs; ci++) if (row[ci]) return false;\n\t\t}\n\t\treturn true;\n\t};\n\tconst mark = (c: number, r: number, cs: number, rs: number) => {\n\t\twhile (occ.length < r + rs) occ.push(new Array(cols).fill(false));\n\t\tfor (let ri = r; ri < r + rs; ri++) for (let ci = c; ci < c + cs; ci++) occ[ri][ci] = true;\n\t};\n\tconst spans = (id: string) => {\n\t\tconst area = getChildLayout(s.boxes.get(id)!.node).gridArea ?? {};\n\t\tconst cs = Math.min(Math.max(toIndex(area.columnSpan) ?? 1, 1), cols);\n\t\tconst rs = Math.min(Math.max(toIndex(area.rowSpan) ?? 1, 1), maxRows);\n\t\tconst col = toIndex(area.column);\n\t\tconst row = toIndex(area.row);\n\t\treturn {\n\t\t\tcol: col === undefined ? undefined : Math.min(col, cols - cs),\n\t\t\trow: row === undefined ? undefined : Math.min(row, maxRows - rs),\n\t\t\tcs,\n\t\t\trs\n\t\t};\n\t};\n\tconst placed: (AutoLayoutGridCell | undefined)[] = new Array(kids.length).fill(undefined);\n\tkids.forEach((id, i) => {\n\t\tconst { col, row, cs, rs } = spans(id);\n\t\tif (col !== undefined && row !== undefined) {\n\t\t\tmark(col, row, cs, rs);\n\t\t\tplaced[i] = { id, column: col, row, columnSpan: cs, rowSpan: rs };\n\t\t}\n\t});\n\tlet cursor: [number, number] = [0, 0]; // [row, column]\n\tkids.forEach((id, i) => {\n\t\tif (placed[i]) return;\n\t\tconst { col, row, cs, rs } = spans(id);\n\t\tconst lastRow = maxRows - rs;\n\t\tlet c = 0;\n\t\tlet r = 0;\n\t\tif (col !== undefined) {\n\t\t\tr = lastRow;\n\t\t\tfor (let ri = 0; ri <= lastRow; ri++) if (free(col, ri, cs, rs)) { r = ri; break; }\n\t\t\tc = col;\n\t\t} else if (row !== undefined) {\n\t\t\tr = row;\n\t\t\tfor (let ci = 0; ci <= cols - cs; ci++) if (free(ci, row, cs, rs)) { c = ci; break; }\n\t\t} else {\n\t\t\tlet [rr, cc] = cursor;\n\t\t\tlet found: [number, number] | undefined;\n\t\t\twhile (rr <= lastRow) {\n\t\t\t\tif (cc + cs <= cols && free(cc, rr, cs, rs)) {\n\t\t\t\t\tfound = [cc, rr];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcc += 1;\n\t\t\t\tif (cc + cs > cols) {\n\t\t\t\t\tcc = 0;\n\t\t\t\t\trr += 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\t[c, r] = found ?? [0, lastRow];\n\t\t\tcursor = [r, c + cs];\n\t\t}\n\t\tmark(c, r, cs, rs);\n\t\tplaced[i] = { id, column: c, row: r, columnSpan: cs, rowSpan: rs };\n\t});\n\tconst cells = placed.filter((c): c is AutoLayoutGridCell => !!c);\n\tconst rows = Math.max(cells.reduce((m, c) => Math.max(m, c.row + c.rowSpan), 0), minRows);\n\treturn { cells, rows: Math.min(rows, MAX_TRACKS) };\n}\n\n/**\n * Resolves track sizes: fixed tracks keep their size, hug tracks fit their content, fill tracks\n * share what is left by their `fr` like CSS (a track never gets less than its content).\n * `avail` is undefined when the frame hugs that axis.\n */\nfunction resolveTracks(tracks: TrackSpec[], content: number[], avail: number | undefined): number[] {\n\tconst out = content.map((c, i) => {\n\t\tconst t = trackSpec(tracks, i);\n\t\treturn t.sizing === 'fixed' ? t.value : c;\n\t});\n\tif (avail === undefined) return out;\n\tconst fr = (i: number) => trackSpec(tracks, i).value;\n\tlet flex = out.map((_, i) => i).filter((i) => trackSpec(tracks, i).sizing === 'fill');\n\tlet used = out.reduce((sum, v, i) => (trackSpec(tracks, i).sizing === 'fill' ? sum : sum + v), 0);\n\twhile (flex.length > 0) {\n\t\tconst total = flex.reduce((sum, i) => sum + fr(i), 0);\n\t\tconst unit = total > 1e-12 ? Math.max(avail - used, 0) / total : 0;\n\t\tconst open = flex.filter((i) => unit * fr(i) >= out[i]);\n\t\tconst frozen = flex.filter((i) => unit * fr(i) < out[i]);\n\t\tif (frozen.length === 0) {\n\t\t\tfor (const i of open) out[i] = unit * fr(i);\n\t\t\tbreak;\n\t\t}\n\t\tused += frozen.reduce((sum, i) => sum + out[i], 0);\n\t\tflex = open;\n\t}\n\treturn out;\n}\n\n/** Content size per track: the largest single-span child; spanning children grow the content-sized tracks they span. */\nfunction trackContent(\n\tcells: AutoLayoutGridCell[],\n\tsizes: number[],\n\ttracks: TrackSpec[],\n\tn: number,\n\tgap: number,\n\tcolumns: boolean,\n\thug: boolean\n): number[] {\n\tconst byContent = (i: number) => trackSpec(tracks, i).sizing !== 'fixed';\n\tconst content = Array.from({ length: n }, (_, i) => {\n\t\tconst t = trackSpec(tracks, i);\n\t\treturn t.sizing === 'fixed' ? t.value : 0;\n\t});\n\tconst span = (c: AutoLayoutGridCell): [number, number] => (columns ? [c.column, c.columnSpan] : [c.row, c.rowSpan]);\n\tcells.forEach((c, k) => {\n\t\tconst [start, len] = span(c);\n\t\tif (len === 1 && byContent(start) && start < n) content[start] = Math.max(content[start], sizes[k]);\n\t});\n\tcells.forEach((c, k) => {\n\t\tconst [start, len] = span(c);\n\t\tif (len < 2) return;\n\t\tconst range = Array.from({ length: len }, (_, j) => start + j).filter((i) => i < n);\n\t\tif (!hug && range.some((i) => trackSpec(tracks, i).sizing === 'fill')) return;\n\t\tconst have = range.reduce((sum, i) => sum + content[i], 0) + gap * (len - 1);\n\t\tconst grow = range.filter(byContent);\n\t\tif (sizes[k] > have && grow.length > 0) {\n\t\t\tconst extra = (sizes[k] - have) / grow.length;\n\t\t\tfor (const i of grow) content[i] += extra;\n\t\t}\n\t});\n\treturn content;\n}\n\nfunction trackRanges(start: number, sizes: number[], gap: number): [number, number][] {\n\tlet x = start;\n\treturn sizes.map((size) => {\n\t\tconst r: [number, number] = [x, x + size];\n\t\tx += size + gap;\n\t\treturn r;\n\t});\n}\n\nfunction gridGeometryOf(s: LayoutState, frame: string): AutoLayoutGridGeometry | undefined {\n\tconst f = s.boxes.get(frame);\n\tconst al = f && getAutoLayout(f.node);\n\tif (!f || !al || al.mode !== 'grid') return undefined;\n\tconst [pt, pr, pb, pl] = resolvePadding(al.padding);\n\tconst kids = flowChildren(s, frame);\n\tconst { cells, rows } = gridPlace(s, al, kids);\n\tconst colSpec = trackList(al.grid?.columns, 2);\n\tconst rowSpec = trackList(al.grid?.rows, 1);\n\tconst cols = colSpec.count;\n\tconst gx = Math.max(finite(al.gap), 0);\n\tconst gy = Math.max(al.rowGap !== undefined ? finite(al.rowGap) : gx, 0);\n\tconst hugW = getLayoutSizing(f.node, 'width') === 'hug';\n\tconst hugH = getLayoutSizing(f.node, 'height') === 'hug';\n\tconst ws: number[] = [];\n\tconst hs: number[] = [];\n\tfor (const c of cells) {\n\t\tconst b = s.boxes.get(c.id)!;\n\t\tconst lim = limitsOf(b.node);\n\t\tws.push(getLayoutSizing(b.node, 'width') === 'fill' && !hugW ? lim.minW ?? 0 : b.w);\n\t\ths.push(getLayoutSizing(b.node, 'height') === 'fill' && !hugH ? lim.minH ?? 0 : b.h);\n\t}\n\tconst colContent = trackContent(cells, ws, colSpec.tracks, cols, gx, true, hugW);\n\tconst rowContent = trackContent(cells, hs, rowSpec.tracks, rows, gy, false, hugH);\n\tconst gapsW = gx * Math.max(cols - 1, 0);\n\tconst gapsH = gy * Math.max(rows - 1, 0);\n\tconst colW = resolveTracks(colSpec.tracks, colContent, hugW ? undefined : finite(f.w) - pl - pr - gapsW);\n\tconst rowH = resolveTracks(rowSpec.tracks, rowContent, hugH ? undefined : finite(f.h) - pt - pb - gapsH);\n\tconst lim = limitsOf(f.node);\n\tconst width = hugW ? colW.reduce((a, b) => a + b, 0) + gapsW + pl + pr : finite(f.w);\n\tconst height = hugH ? rowH.reduce((a, b) => a + b, 0) + gapsH + pt + pb : finite(f.h);\n\treturn {\n\t\tcolumns: trackRanges(pl, colW, gx),\n\t\trows: trackRanges(pt, rowH, gy),\n\t\tcells,\n\t\twidth: Math.max(clampLayoutSize(width, lim.minW, lim.maxW), pl + pr),\n\t\theight: Math.max(clampLayoutSize(height, lim.minH, lim.maxH), pt + pb)\n\t};\n}\n\nfunction layoutGrid(s: LayoutState, frame: string, al: AutoLayout) {\n\tconst f = s.boxes.get(frame)!;\n\tconst fw = f.w;\n\tconst fh = f.h;\n\tconst hugW = getLayoutSizing(f.node, 'width') === 'hug';\n\tconst hugH = getLayoutSizing(f.node, 'height') === 'hug';\n\tconst lim = limitsOf(f.node);\n\tconst [pt, pr, pb, pl] = resolvePadding(al.padding);\n\tsetSize(\n\t\ts,\n\t\tframe,\n\t\thugW ? fw : Math.max(clampLayoutSize(fw, lim.minW, lim.maxW), pl + pr),\n\t\thugH ? fh : Math.max(clampLayoutSize(fh, lim.minH, lim.maxH), pt + pb)\n\t);\n\tlet g = gridGeometryOf(s, frame);\n\tif (!g) return;\n\t// Fill-width children take their column width first, so wrapping text sizes its row.\n\tlet widened = false;\n\tfor (const c of g.cells) {\n\t\tconst b = s.boxes.get(c.id)!;\n\t\tif (getLayoutSizing(b.node, 'width') === 'fill' && !hugW) {\n\t\t\tconst l = limitsOf(b.node);\n\t\t\tresizeChild(s, c.id, clampLayoutSize(cellRect(g, c).width, l.minW, l.maxW), b.h);\n\t\t\twidened = true;\n\t\t}\n\t}\n\tif (widened) g = gridGeometryOf(s, frame) ?? g;\n\tsetSize(s, frame, g.width, g.height);\n\tconstrain(s, frame, fw, fh);\n\tconst frameX: AutoLayoutCellAlign = al.alignMain === 'center' ? 'center' : al.alignMain === 'end' ? 'end' : 'start';\n\tconst frameY: AutoLayoutCellAlign = al.alignCross === 'center' ? 'center' : al.alignCross === 'end' ? 'end' : 'start';\n\tconst offset = (a: AutoLayoutCellAlign, free: number) => (a === 'center' ? free / 2 : a === 'end' ? free : 0);\n\tfor (const c of g.cells) {\n\t\tconst cell = cellRect(g, c);\n\t\tconst b = s.boxes.get(c.id)!;\n\t\tconst l = limitsOf(b.node);\n\t\tconst area = getChildLayout(b.node).gridArea ?? {};\n\t\tconst fillW = getLayoutSizing(b.node, 'width') === 'fill' && !hugW;\n\t\tconst fillH = getLayoutSizing(b.node, 'height') === 'fill' && !hugH;\n\t\tif (fillW || fillH) {\n\t\t\tresizeChild(\n\t\t\t\ts,\n\t\t\t\tc.id,\n\t\t\t\tfillW ? clampLayoutSize(cell.width, l.minW, l.maxW) : b.w,\n\t\t\t\tfillH ? clampLayoutSize(cell.height, l.minH, l.maxH) : b.h\n\t\t\t);\n\t\t}\n\t\tconst ox = offset(area.alignX ?? frameX, cell.width - b.w);\n\t\tconst oy = offset(area.alignY ?? frameY, cell.height - b.h);\n\t\tplace(s, c.id, finite(cell.x + ox), finite(cell.y + oy));\n\t}\n}\n\n// ---------------------------------------------------------------------------------------------\n// Constraints\n\n/**\n * After `id` changed size from `ow × oh`, moves / resizes its children by their constraints\n * (recursively). In an auto layout frame only absolute children follow constraints.\n */\nfunction constrain(s: LayoutState, id: string, ow: number, oh: number) {\n\tconst f = s.boxes.get(id);\n\tif (!f) return;\n\tconst nw = f.w;\n\tconst nh = f.h;\n\tif (Math.abs(nw - ow) < 1e-9 && Math.abs(nh - oh) < 1e-9) return;\n\tconst absoluteOnly = !!getAutoLayout(f.node);\n\tconst sx = ow > 1e-9 ? nw / ow : 1;\n\tconst sy = oh > 1e-9 ? nh / oh : 1;\n\ts.guard(`constrain:${id}`, () => {\n\t\tfor (const k of f.kids) {\n\t\t\tconst c = s.boxes.get(k)!;\n\t\t\tconst child = getChildLayout(c.node);\n\t\t\tif (absoluteOnly && !child.absolute) continue;\n\t\t\tlet x0 = c.x;\n\t\t\tlet x1 = c.x + c.w;\n\t\t\tlet y0 = c.y;\n\t\t\tlet y1 = c.y + c.h;\n\t\t\tconst dx = nw - ow;\n\t\t\tconst dy = nh - oh;\n\t\t\tswitch (child.constraints?.horizontal ?? 'left') {\n\t\t\t\tcase 'right':\n\t\t\t\t\tx0 += dx;\n\t\t\t\t\tx1 += dx;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'leftRight':\n\t\t\t\t\tx1 += dx;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'center':\n\t\t\t\t\tx0 += dx / 2;\n\t\t\t\t\tx1 += dx / 2;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'scale':\n\t\t\t\t\tx0 *= sx;\n\t\t\t\t\tx1 *= sx;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tswitch (child.constraints?.vertical ?? 'top') {\n\t\t\t\tcase 'bottom':\n\t\t\t\t\ty0 += dy;\n\t\t\t\t\ty1 += dy;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'topBottom':\n\t\t\t\t\ty1 += dy;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'center':\n\t\t\t\t\ty0 += dy / 2;\n\t\t\t\t\ty1 += dy / 2;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'scale':\n\t\t\t\t\ty0 *= sy;\n\t\t\t\t\ty1 *= sy;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tconst cw = c.w;\n\t\t\tconst ch = c.h;\n\t\t\tconst resized = Math.abs(x1 - x0 - cw) > 1e-9 || Math.abs(y1 - y0 - ch) > 1e-9;\n\t\t\tplace(s, k, finite(x0), finite(y0));\n\t\t\tif (resized) {\n\t\t\t\tsetSize(s, k, x1 - x0, y1 - y0);\n\t\t\t\tif (c.kids.length > 0) constrain(s, k, cw, ch);\n\t\t\t\tif (getAutoLayout(c.node)) layout(s, k);\n\t\t\t}\n\t\t}\n\t});\n}\n\n// ---------------------------------------------------------------------------------------------\n// Public API\n\n/**\n * Lays out one auto layout frame's children and sizes the frame when it hugs (nested frames\n * that are resized by the layout are laid out again). Returns the position / size updates.\n */\nexport function layoutFrame(nodes: CanvasNode[], frameId: string, options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\tconst s = new LayoutState(nodes, options);\n\tlayout(s, frameId);\n\treturn s.updates();\n}\n\nfunction postOrder(s: LayoutState, nodes: CanvasNode[]): string[] {\n\tconst out: string[] = [];\n\tconst seen = new Set<string>();\n\tconst visit = (id: string) => {\n\t\tif (seen.has(id)) return;\n\t\tseen.add(id);\n\t\tfor (const k of s.boxes.get(id)?.kids ?? []) visit(k);\n\t\tout.push(id);\n\t};\n\tconst roots = nodes.filter((n) => !n.parentId || !s.boxes.has(n.parentId));\n\tfor (const r of roots) visit(r.id);\n\tfor (const n of nodes) visit(n.id); // nodes in parent cycles\n\treturn out;\n}\n\n/** Lays out every auto layout frame, children before parents, so nested hugging settles in one pass. */\nexport function layoutAll(nodes: CanvasNode[], options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\tconst s = new LayoutState(nodes, options);\n\tfor (const id of postOrder(s, nodes)) if (getAutoLayout(s.boxes.get(id)!.node)) layout(s, id);\n\treturn s.updates();\n}\n\n/**\n * Lays out `changedId` (when it is a frame) and then each of its auto layout ancestors,\n * innermost first. Use it after a node inside a frame was added, removed, resized or moved.\n */\nexport function layoutUp(nodes: CanvasNode[], changedId: string, options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\treturn layoutUpMany(nodes, [changedId], options);\n}\n\n/** {@link layoutUp} for several changed nodes at once (each chain once, innermost frames first). */\nexport function layoutUpMany(nodes: CanvasNode[], changedIds: Iterable<string>, options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\tconst s = new LayoutState(nodes, options);\n\tconst chains = [...new Set(changedIds)].filter((id) => s.boxes.has(id)).map((id) => [id, ...s.ancestors(id)]);\n\t// Deeper frames first so a parent sees its children's final sizes.\n\tconst depth = new Map<string, number>();\n\tfor (const chain of chains) chain.forEach((id, i) => depth.set(id, Math.max(depth.get(id) ?? 0, chain.length - 1 - i)));\n\tconst order = [...depth.keys()].sort((a, b) => depth.get(b)! - depth.get(a)!);\n\tfor (const id of order) if (getAutoLayout(s.boxes.get(id)!.node)) layout(s, id);\n\treturn s.updates();\n}\n\n/**\n * Children of `parentId` follow their constraints after the parent was resized from\n * `previousSize` to its current size. In an auto layout frame only absolute children move\n * (the layout places the others). Returns the updates.\n */\nexport function applyConstraints(\n\tnodes: CanvasNode[],\n\tparentId: string,\n\tpreviousSize: { width: number; height: number },\n\toptions: AutoLayoutOptions = {}\n): CanvasNodeUpdate[] {\n\tconst s = new LayoutState(nodes, options);\n\tconstrain(s, parentId, previousSize.width, previousSize.height);\n\treturn s.updates();\n}\n\n/** The resolved grid of a grid frame (frame-local), from its children's current sizes. */\nexport function getAutoLayoutGrid(\n\tnodes: CanvasNode[],\n\tframeId: string,\n\toptions: AutoLayoutOptions = {}\n): AutoLayoutGridGeometry | undefined {\n\treturn gridGeometryOf(new LayoutState(nodes, options), frameId);\n}\n\n/** Ids of the children a frame places (visible, not absolute), in order. */\nexport function getFlowChildren(nodes: CanvasNode[], frameId: string): string[] {\n\treturn nodes\n\t\t.filter((n) => n.parentId === frameId && n.id !== frameId && n.hidden !== true && !getChildLayout(n).absolute)\n\t\t.map((n) => n.id);\n}\n\n/** The nearest auto layout frame that places `id` (its parent, when `id` is a flow child). */\nexport function getLayoutParent(nodes: CanvasNode[], id: string): CanvasNode | undefined {\n\tconst lookup = createNodeLookup(nodes);\n\tconst node = lookup.get(id);\n\tif (!node?.parentId || getChildLayout(node).absolute) return undefined;\n\tconst parent = lookup.get(node.parentId);\n\treturn isAutoLayoutFrame(parent) ? parent : undefined;\n}\n\n/**\n * Makes a node's sizing coherent with where it sits, and that of its children: `fill` needs an\n * auto layout parent that does not hug that axis (and a flow child), `hug` needs an auto layout\n * frame or a text-like node (see `measure.textSize`); otherwise the axis becomes `fixed`.\n * Writes `data.childLayout.sizing`. Returns a new array (unchanged nodes keep their identity).\n */\nexport function makeSizingCoherent<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tid: string,\n\toptions: AutoLayoutOptions = {}\n): TNode[] {\n\tconst lookup = createNodeLookup(nodes);\n\tconst targets = new Set([id, ...nodes.filter((n) => n.parentId === id).map((n) => n.id)]);\n\tif (!lookup.has(id)) return nodes;\n\tlet changed = false;\n\tconst next = nodes.map((node) => {\n\t\tif (!targets.has(node.id)) return node;\n\t\tconst parent = node.parentId ? lookup.get(node.parentId) : undefined;\n\t\tconst flowParent = parent && isAutoLayoutFrame(parent) && !getChildLayout(node).absolute ? parent : undefined;\n\t\tconst canHug = isAutoLayoutFrame(node) || options.measure?.textSize?.(node) !== undefined;\n\t\tconst coherent = (axis: Axis): AutoLayoutSizing => {\n\t\t\tconst sizing = getLayoutSizing(node, axis);\n\t\t\tif (sizing === 'fill' && (!flowParent || getLayoutSizing(flowParent, axis) === 'hug')) return 'fixed';\n\t\t\tif (sizing === 'hug' && !canHug) return 'fixed';\n\t\t\treturn sizing;\n\t\t};\n\t\tconst width = coherent('width');\n\t\tconst height = coherent('height');\n\t\tif (width === getLayoutSizing(node, 'width') && height === getLayoutSizing(node, 'height')) return node;\n\t\tchanged = true;\n\t\tconst child = getChildLayout(node);\n\t\treturn {\n\t\t\t...node,\n\t\t\tdata: { ...node.data, [CHILD_LAYOUT_DATA_KEY]: { ...child, sizing: { ...child.sizing, width, height } } }\n\t\t};\n\t});\n\treturn changed ? next : nodes;\n}\n\n// ---------------------------------------------------------------------------------------------\n// Reordering\n\nexport interface AutoLayoutInsertTarget {\n\tframeId: string;\n\t/** Index among the frame's flow children, not counting the dragged node. */\n\tindex: number;\n\t/** Insertion marker (absolute flow coordinates) for `<CanvasGuidesLayer>`. */\n\tguide: GuideLine;\n}\n\n/**\n * Where a node dropped at `point` (absolute flow coordinates) goes among the flow children of\n * auto layout frame `frameId`: the reading-order index of the slot under the point. `excludeId`\n * (the dragged node) is left out of the siblings.\n */\nexport function getAutoLayoutInsertTarget(\n\tnodes: CanvasNode[],\n\tframeId: string,\n\tpoint: XYPosition,\n\toptions: AutoLayoutOptions & { excludeId?: string } = {}\n): AutoLayoutInsertTarget | undefined {\n\tconst lookup = createNodeLookup(nodes);\n\tconst frame = lookup.get(frameId);\n\tconst al = getAutoLayout(frame);\n\tif (!frame || !al) return undefined;\n\tconst s = new LayoutState(nodes, options);\n\tconst origin = getAbsolutePosition(frame, lookup);\n\tconst px = point.x - origin.x;\n\tconst py = point.y - origin.y;\n\tconst siblings = flowChildren(s, frameId)\n\t\t.filter((id) => id !== options.excludeId)\n\t\t.map((id) => s.boxes.get(id)!);\n\t// Grid and wrapped layouts read in rows (vertical wrap: columns); plain stacks along their axis.\n\tconst horizontal = al.mode !== 'vertical';\n\tconst main = (b: { x: number; y: number }) => (horizontal ? b.x : b.y);\n\tconst mainSize = (b: Box) => (horizontal ? b.w : b.h);\n\tconst cross = (b: { x: number; y: number }) => (horizontal ? b.y : b.x);\n\tconst crossSize = (b: Box) => (horizontal ? b.h : b.w);\n\tconst p = { x: px, y: py };\n\tconst multiLine = al.mode === 'grid' || !!al.wrap;\n\tlet index = 0;\n\tfor (const b of siblings) {\n\t\tconst before = multiLine\n\t\t\t? cross(b) + crossSize(b) <= cross(p) ||\n\t\t\t\t(cross(b) <= cross(p) && main(b) + mainSize(b) / 2 < main(p))\n\t\t\t: main(b) + mainSize(b) / 2 < main(p);\n\t\tif (before) index += 1;\n\t}\n\tconst [pt, pr, pb, pl] = resolvePadding(al.padding);\n\tconst prev = siblings[index - 1];\n\tconst next = siblings[index];\n\tconst f = s.boxes.get(frameId)!;\n\tlet guide: GuideLine;\n\tif (horizontal) {\n\t\tconst x = prev && next && Math.abs(cross(prev) - cross(next)) < 1e-6\n\t\t\t? (prev.x + prev.w + next.x) / 2\n\t\t\t: prev\n\t\t\t\t? prev.x + prev.w\n\t\t\t\t: next\n\t\t\t\t\t? next.x\n\t\t\t\t\t: pl;\n\t\tconst ref = next ?? prev;\n\t\tconst y1 = ref ? ref.y : pt;\n\t\tconst y2 = ref ? ref.y + ref.h : f.h - pb;\n\t\tguide = { orientation: 'vertical', kind: 'edge', x1: origin.x + x, y1: origin.y + y1, x2: origin.x + x, y2: origin.y + y2 };\n\t} else {\n\t\tconst y = prev && next && Math.abs(cross(prev) - cross(next)) < 1e-6\n\t\t\t? (prev.y + prev.h + next.y) / 2\n\t\t\t: prev\n\t\t\t\t? prev.y + prev.h\n\t\t\t\t: next\n\t\t\t\t\t? next.y\n\t\t\t\t\t: pt;\n\t\tconst ref = next ?? prev;\n\t\tconst x1 = ref ? ref.x : pl;\n\t\tconst x2 = ref ? ref.x + ref.w : f.w - pr;\n\t\tguide = { orientation: 'horizontal', kind: 'edge', x1: origin.x + x1, y1: origin.y + y, x2: origin.x + x2, y2: origin.y + y };\n\t}\n\treturn { frameId, index, guide };\n}\n\n/**\n * Moves child `childId` of an auto layout frame to flow index `index` (among its flow siblings)\n * by reordering the node array; parents still precede their children. Returns a new array\n * (the same array when nothing moves).\n */\nexport function moveLayoutChild<TNode extends CanvasNode>(nodes: TNode[], childId: string, index: number): TNode[] {\n\tconst child = nodes.find((n) => n.id === childId);\n\tif (!child?.parentId) return nodes;\n\tconst siblings = nodes.filter((n) => n.parentId === child.parentId && n.id !== child.parentId);\n\tconst flow = siblings.filter((n) => n.hidden !== true && !getChildLayout(n).absolute && n.id !== childId);\n\tconst clamped = Math.min(Math.max(Math.floor(Number.isFinite(index) ? index : 0), 0), flow.length);\n\t// Insert before the flow sibling now at `clamped` (or after the last one).\n\tconst order = siblings.filter((n) => n.id !== childId);\n\tconst anchor = flow[clamped];\n\tconst at = anchor ? order.indexOf(anchor) : flow.length > 0 ? order.indexOf(flow[flow.length - 1]) + 1 : order.length;\n\torder.splice(at, 0, child);\n\tif (order.every((n, i) => n === siblings[i])) return nodes;\n\t// Put the siblings back into the slots they occupied, in their new order.\n\tlet k = 0;\n\tconst parentId = child.parentId;\n\tconst placed = nodes.map((n) => (n.parentId === parentId && n.id !== parentId ? order[k++] : n));\n\treturn parentsFirst(placed);\n}\n\n/** Stable reorder so every node comes after its parent (xyflow requires it). */\nfunction parentsFirst<TNode extends CanvasNode>(nodes: TNode[]): TNode[] {\n\tconst ids = new Set(nodes.map((n) => n.id));\n\tconst pending = new Map<string, TNode[]>();\n\tconst done = new Set<string>();\n\tconst out: TNode[] = [];\n\tconst emit = (n: TNode) => {\n\t\tout.push(n);\n\t\tdone.add(n.id);\n\t\tconst waiting = pending.get(n.id);\n\t\tif (waiting) {\n\t\t\tpending.delete(n.id);\n\t\t\tfor (const w of waiting) emit(w);\n\t\t}\n\t};\n\tfor (const n of nodes) {\n\t\tif (n.parentId && ids.has(n.parentId) && !done.has(n.parentId) && n.parentId !== n.id) {\n\t\t\tif (!pending.has(n.parentId)) pending.set(n.parentId, []);\n\t\t\tpending.get(n.parentId)!.push(n);\n\t\t} else emit(n);\n\t}\n\tfor (const waiting of pending.values()) for (const n of waiting) if (!done.has(n.id)) emit(n); // cycles\n\treturn out;\n}\n","import type { Command } from '../history/types.js';\nimport {\n\talignNodes,\n\tapplyNodeUpdates,\n\tdistributeNodes,\n\tinvertNodeUpdates,\n\tisLocked,\n\tlockNodes,\n\tmatchSize,\n\treorderNodes,\n\tunlockNodes,\n\ttype AlignEdge,\n\ttype AlignNodesOptions,\n\ttype CanvasNodeUpdate,\n\ttype DistributeAxis,\n\ttype DistributeMode,\n\ttype DistributeNodesOptions,\n\ttype MatchSizeOptions,\n\ttype NodeRectOptions,\n\ttype SizeDimension,\n\ttype ZOrderOperation\n} from './arrange.js';\nimport type { CanvasArrangeAction } from './arrangeShortcuts.js';\nimport {\n\tapplyShapeCombination,\n\tapplyShapeReplacement,\n\tcanCombineShapeNodes,\n\tcanOffsetShapeNodes,\n\tcanOutlineShapeNodeStrokes,\n\tcombineShapeNodes,\n\toffsetShapeNodes,\n\toutlineShapeNodeStrokes,\n\trevertShapeCombination,\n\ttype CombineShapeNodesOptions,\n\ttype CombineShapeNodesResult,\n\ttype OffsetShapeNodesOptions,\n\ttype OutlineShapeStrokesOptions,\n\ttype ShapeOperation,\n\ttype ShapePathEditResult\n} from './shapeNodes.js';\nimport {\n\tcreateNodeGuideIndex,\n\tsnapNodeDrag,\n\ttype GuideIndex,\n\ttype GuideLine,\n\ttype SnapGuidesOptions,\n\ttype SnapNodeDragResult\n} from './guides.js';\nimport {\n\tgetResizingNodeRect,\n\tinferResizeHandle,\n\tsnapNodeResize,\n\ttype ResizeHandle,\n\ttype SnapNodeResizeResult\n} from './resizeSnap.js';\nimport { createNodeLookup } from './arrange.js';\nimport { layoutUpMany, type AutoLayoutOptions } from './autoLayout.js';\nimport type { CanvasNode, Rect, XYPosition } from './types.js';\n\n/** Minimal history surface (`HistoryManager` satisfies it). */\nexport interface ArrangeHistoryTarget {\n\texecute(command: Command): void;\n}\n\nexport interface CanvasArrangeControllerOptions<TNode extends CanvasNode = CanvasNode> {\n\tgetNodes: () => TNode[];\n\tsetNodes: (nodes: TNode[]) => void;\n\t/** Ids acted on when a method is called without ids (default: nodes with `selected: true`). */\n\tgetSelectedIds?: () => string[];\n\t/** When set, every arrange action is recorded as one undoable command. */\n\thistory?: ArrangeHistoryTarget;\n\t/** Current zoom, used to convert the guide threshold from screen pixels (default 1). */\n\tgetZoom?: () => number;\n\t/** Smart-guide options (threshold in screen px, edges / centres / spacing toggles). */\n\tguides?: Omit<SnapGuidesOptions, 'zoom'>;\n\t/** Turns drag snapping off while keeping the arrange actions (default `true`). */\n\tsnapping?: boolean;\n\t/** Turns resize snapping off (default: follows `snapping`). */\n\tresizeSnapping?: boolean;\n\t/** Resize options passed to `snapResize` (`keepAspectRatio`, `minWidth`, `minHeight`). */\n\tresize?: { keepAspectRatio?: boolean; minWidth?: number; minHeight?: number };\n\t/** Size resolution for unmeasured nodes. */\n\trectOptions?: NodeRectOptions;\n\t/** Called whenever the guide segments change (set while dragging, cleared on drop). */\n\tonGuidesChange?: (guides: GuideLine[]) => void;\n\t/**\n\t * Re-run auto layout (`data.layout` frames) around the nodes an arrange action moves or\n\t * resizes, in the same undoable step. `true` uses the default measuring.\n\t */\n\tautoLayout?: boolean | AutoLayoutOptions;\n}\n\n/**\n * A React Flow / SvelteFlow style node change; only `position` changes are inspected.\n * `id` is optional because a batch can contain changes without one (React Flow's\n * `add` change carries an `item` instead), so `NodeChange[]` must be accepted as is.\n */\nexport interface PositionChangeLike {\n\ttype: string;\n\tid?: string;\n\tposition?: XYPosition;\n\tdragging?: boolean;\n\t[key: string]: any;\n}\n\n/**\n * Framework-agnostic arrange controller shared by the Svelte rune and the React hook:\n * align / distribute / match size / lock / z-order actions on the selection (optionally\n * undoable through a `HistoryManager`) and smart-guide snapping during drags.\n */\nexport function createCanvasArrangeController<TNode extends CanvasNode = CanvasNode>(\n\toptions: CanvasArrangeControllerOptions<TNode>\n) {\n\tlet guides: GuideLine[] = [];\n\tlet dragIndex: GuideIndex | null = null;\n\tlet dragKey = '';\n\t// Snapped positions from the last dragging batch, re-applied to the drag-stop change.\n\tlet lastSnapped = new Map<string, XYPosition>();\n\t// Resize snapping: the node, its box when the resize started, cached targets and the\n\t// last snapped box (absolute) re-applied when the flow writes the raw end size.\n\tlet resizeId: string | null = null;\n\tlet resizeStart: Rect | null = null;\n\tlet resizeIndex: GuideIndex | null = null;\n\tlet lastResized: SnapNodeResizeResult | null = null;\n\n\tconst selectedIds = (ids?: Iterable<string>) =>\n\t\tids ? [...ids] : options.getSelectedIds?.() ?? options.getNodes().filter((n) => n.selected).map((n) => n.id);\n\n\tconst setGuides = (next: GuideLine[]) => {\n\t\tif (next.length === 0 && guides.length === 0) return;\n\t\tguides = next;\n\t\toptions.onGuidesChange?.(guides);\n\t};\n\n\t/** Applies updates as one (optionally undoable) step. Returns the updates. */\n\tfunction apply(name: string, updates: CanvasNodeUpdate[]): CanvasNodeUpdate[] {\n\t\tif (updates.length === 0) return updates;\n\t\tif (options.autoLayout) {\n\t\t\tconst layoutOptions = options.autoLayout === true ? { rectOptions: options.rectOptions } : options.autoLayout;\n\t\t\tconst moved = applyNodeUpdates(options.getNodes(), updates);\n\t\t\tconst layout = layoutUpMany(moved, updates.map((u) => u.id), layoutOptions);\n\t\t\tif (layout.length > 0) {\n\t\t\t\tconst byId = new Map<string, CanvasNodeUpdate>();\n\t\t\t\tfor (const u of [...updates, ...layout]) byId.set(u.id, { ...byId.get(u.id), ...u });\n\t\t\t\tupdates = [...byId.values()];\n\t\t\t}\n\t\t}\n\t\tconst inverse = invertNodeUpdates(options.getNodes(), updates);\n\t\tconst run = (u: CanvasNodeUpdate[]) => options.setNodes(applyNodeUpdates(options.getNodes(), u));\n\t\tif (options.history) {\n\t\t\toptions.history.execute({ name, execute: () => run(updates), undo: () => run(inverse) });\n\t\t} else {\n\t\t\trun(updates);\n\t\t}\n\t\treturn updates;\n\t}\n\n\tfunction setLocked(name: string, ids: string[], locked: boolean): string[] {\n\t\tconst nodes = options.getNodes();\n\t\tconst set = new Set(ids);\n\t\tconst changed = nodes.filter((n) => set.has(n.id) && isLocked(n) !== locked).map((n) => n.id);\n\t\tif (changed.length === 0) return changed;\n\t\tconst lock = () => options.setNodes(lockNodes(options.getNodes(), changed));\n\t\tconst unlock = () => options.setNodes(unlockNodes(options.getNodes(), changed));\n\t\tif (options.history) {\n\t\t\toptions.history.execute({ name, execute: locked ? lock : unlock, undo: locked ? unlock : lock });\n\t\t} else if (locked) lock();\n\t\telse unlock();\n\t\treturn changed;\n\t}\n\n\tconst rect = () => options.rectOptions ?? {};\n\n\tconst controller = {\n\t\tget guides() {\n\t\t\treturn guides;\n\t\t},\n\t\tapply,\n\t\talign(edge: AlignEdge, ids?: Iterable<string>, alignOptions: AlignNodesOptions = {}) {\n\t\t\treturn apply(`Align ${edge}`, alignNodes(options.getNodes(), selectedIds(ids), edge, { ...rect(), ...alignOptions }));\n\t\t},\n\t\tdistribute(\n\t\t\taxis: DistributeAxis,\n\t\t\tmode: DistributeMode = 'gaps',\n\t\t\tids?: Iterable<string>,\n\t\t\tdistributeOptions: DistributeNodesOptions = {}\n\t\t) {\n\t\t\treturn apply(\n\t\t\t\t`Distribute ${axis}`,\n\t\t\t\tdistributeNodes(options.getNodes(), selectedIds(ids), axis, mode, { ...rect(), ...distributeOptions })\n\t\t\t);\n\t\t},\n\t\tmatchSize(dimension: SizeDimension, ids?: Iterable<string>, sizeOptions: MatchSizeOptions = {}) {\n\t\t\treturn apply(\n\t\t\t\t'Make Same Size',\n\t\t\t\tmatchSize(options.getNodes(), selectedIds(ids), dimension, { ...rect(), ...sizeOptions })\n\t\t\t);\n\t\t},\n\t\treorder(operation: ZOrderOperation, ids?: Iterable<string>) {\n\t\t\treturn apply(operation, reorderNodes(options.getNodes(), selectedIds(ids), operation, rect()));\n\t\t},\n\t\tbringForward: (ids?: Iterable<string>) => controller.reorder('bringForward', ids),\n\t\tsendBackward: (ids?: Iterable<string>) => controller.reorder('sendBackward', ids),\n\t\tbringToFront: (ids?: Iterable<string>) => controller.reorder('bringToFront', ids),\n\t\tsendToBack: (ids?: Iterable<string>) => controller.reorder('sendToBack', ids),\n\t\tlock: (ids?: Iterable<string>) => setLocked('Lock', selectedIds(ids), true),\n\t\tunlock: (ids?: Iterable<string>) => setLocked('Unlock', selectedIds(ids), false),\n\t\ttoggleLock(ids?: Iterable<string>) {\n\t\t\tconst list = selectedIds(ids);\n\t\t\tconst set = new Set(list);\n\t\t\tconst anyUnlocked = options.getNodes().some((n) => set.has(n.id) && !isLocked(n));\n\t\t\treturn anyUnlocked ? controller.lock(list) : controller.unlock(list);\n\t\t},\n\t\tisLocked: (id: string) => isLocked(options.getNodes().find((n) => n.id === id)),\n\n\t\t/**\n\t\t * Freeform shape operation (Unite / Intersect / Subtract / Exclude / Divide) on the\n\t\t * shape nodes among `ids` (default: the selection). Replaces them with the result as\n\t\t * one undoable step. Refuses without changing anything when a selected shape is\n\t\t * locked, fewer than two shapes are selected, or the result is empty.\n\t\t */\n\t\tcombineShapes(\n\t\t\toperation: ShapeOperation,\n\t\t\tids?: Iterable<string>,\n\t\t\tcombineOptions: CombineShapeNodesOptions = {}\n\t\t): CombineShapeNodesResult<TNode> {\n\t\t\tconst before = options.getNodes();\n\t\t\tconst result = combineShapeNodes(before, selectedIds(ids), operation, { ...rect(), ...combineOptions });\n\t\t\tif (!result.ok) return result;\n\t\t\tconst name = `${operation[0].toUpperCase()}${operation.slice(1)} Shapes`;\n\t\t\tconst run = () => options.setNodes(applyShapeCombination(options.getNodes(), result));\n\t\t\tconst undo = () => options.setNodes(revertShapeCombination(options.getNodes(), result, before));\n\t\t\tif (options.history) options.history.execute({ name, execute: run, undo });\n\t\t\telse run();\n\t\t\treturn result;\n\t\t},\n\t\tunite: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('unite', ids, o),\n\t\tintersect: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('intersect', ids, o),\n\t\tsubtract: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('subtract', ids, o),\n\t\texclude: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('exclude', ids, o),\n\t\tdivide: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('divide', ids, o),\n\t\t/** Whether the shape operations can run on `ids` (default: the selection). */\n\t\tcanCombineShapes: (ids?: Iterable<string>) => canCombineShapeNodes(options.getNodes(), selectedIds(ids)).ok,\n\n\t\t/**\n\t\t * Offset Path: grows (`distance > 0`) or shrinks (`distance < 0`) every shape among\n\t\t * `ids` (default: the selection) by `distance` flow units, keeping curves, as one\n\t\t * undoable step. Refuses without changing anything when a shape is locked, no shape is\n\t\t * selected or every result is empty.\n\t\t */\n\t\toffsetShapes(\n\t\t\tdistance: number,\n\t\t\tids?: Iterable<string>,\n\t\t\toffsetOptions: OffsetShapeNodesOptions = {}\n\t\t): ShapePathEditResult<TNode> {\n\t\t\tconst before = options.getNodes();\n\t\t\tconst result = offsetShapeNodes(before, selectedIds(ids), distance, { ...rect(), ...offsetOptions });\n\t\t\tif (!result.ok) return result;\n\t\t\tconst run = () => options.setNodes(applyShapeReplacement(options.getNodes(), result));\n\t\t\tconst undo = () => options.setNodes(revertShapeCombination(options.getNodes(), result, before));\n\t\t\tif (options.history) options.history.execute({ name: 'Offset Path', execute: run, undo });\n\t\t\telse run();\n\t\t\treturn result;\n\t\t},\n\t\t/** Whether `offsetShapes` can run on `ids` (default: the selection). */\n\t\tcanOffsetShapes: (ids?: Iterable<string>) => canOffsetShapeNodes(options.getNodes(), selectedIds(ids)).ok,\n\n\t\t/**\n\t\t * Outline Stroke: converts the strokes of the shapes among `ids` (default: the\n\t\t * selection) to filled path shapes, as one undoable step. Refuses when a shape is\n\t\t * locked or no selected shape has a visible stroke.\n\t\t */\n\t\toutlineStroke(ids?: Iterable<string>, outlineOptions: OutlineShapeStrokesOptions = {}): ShapePathEditResult<TNode> {\n\t\t\tconst before = options.getNodes();\n\t\t\tconst result = outlineShapeNodeStrokes(before, selectedIds(ids), { ...rect(), ...outlineOptions });\n\t\t\tif (!result.ok) return result;\n\t\t\tconst run = () => options.setNodes(applyShapeReplacement(options.getNodes(), result));\n\t\t\tconst undo = () => options.setNodes(revertShapeCombination(options.getNodes(), result, before));\n\t\t\tif (options.history) options.history.execute({ name: 'Outline Stroke', execute: run, undo });\n\t\t\telse run();\n\t\t\treturn result;\n\t\t},\n\t\t/** Whether `outlineStroke` can run on `ids` (default: the selection). */\n\t\tcanOutlineStroke: (ids?: Iterable<string>) => canOutlineShapeNodeStrokes(options.getNodes(), selectedIds(ids)).ok,\n\n\t\t/**\n\t\t * Runs a shortcut action. Scene actions are not handled here (see the scenes store);\n\t\t * returns `false` for them and for unknown actions.\n\t\t */\n\t\trunAction(action: CanvasArrangeAction): boolean {\n\t\t\tswitch (action) {\n\t\t\t\tcase 'alignLeft': controller.align('left'); return true;\n\t\t\t\tcase 'alignCenter': controller.align('center'); return true;\n\t\t\t\tcase 'alignRight': controller.align('right'); return true;\n\t\t\t\tcase 'alignTop': controller.align('top'); return true;\n\t\t\t\tcase 'alignMiddle': controller.align('middle'); return true;\n\t\t\t\tcase 'alignBottom': controller.align('bottom'); return true;\n\t\t\t\tcase 'distributeHorizontal': controller.distribute('horizontal'); return true;\n\t\t\t\tcase 'distributeVertical': controller.distribute('vertical'); return true;\n\t\t\t\tcase 'lock': controller.lock(); return true;\n\t\t\t\tcase 'unlock': controller.unlock(); return true;\n\t\t\t\tcase 'bringForward': controller.bringForward(); return true;\n\t\t\t\tcase 'sendBackward': controller.sendBackward(); return true;\n\t\t\t\tcase 'bringToFront': controller.bringToFront(); return true;\n\t\t\t\tcase 'sendToBack': controller.sendToBack(); return true;\n\t\t\t\tdefault: return false;\n\t\t\t}\n\t\t},\n\n\t\t/** Caches the snap targets for a drag. Optional: `drag` builds them lazily. */\n\t\tbeginDrag(ids?: Iterable<string>) {\n\t\t\tconst list = selectedIds(ids);\n\t\t\tdragKey = list.slice().sort().join('\\u0000');\n\t\t\tdragIndex = createNodeGuideIndex(options.getNodes(), list, rect());\n\t\t},\n\n\t\t/**\n\t\t * Snaps the dragged nodes (as currently positioned in `getNodes()`) to the other\n\t\t * nodes, writes the snapped positions back through `setNodes` (not recorded in\n\t\t * history) and updates `guides`.\n\t\t */\n\t\tdrag(ids?: Iterable<string>): SnapNodeDragResult | null {\n\t\t\tif (options.snapping === false) return null;\n\t\t\tconst list = selectedIds(ids);\n\t\t\tconst key = list.slice().sort().join('\\u0000');\n\t\t\tif (!dragIndex || key !== dragKey) controller.beginDrag(list);\n\t\t\tconst result = snapNodeDrag(options.getNodes(), list, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: dragIndex!\n\t\t\t});\n\t\t\tlastSnapped = new Map();\n\t\t\tif (result.updates.length > 0) {\n\t\t\t\toptions.setNodes(applyNodeUpdates(options.getNodes(), result.updates));\n\t\t\t\tfor (const u of result.updates) if (u.position) lastSnapped.set(u.id, u.position);\n\t\t\t}\n\t\t\tsetGuides(result.guides);\n\t\t\treturn result;\n\t\t},\n\n\t\t/**\n\t\t * React Flow style: adjusts dragging `position` changes in an `onNodesChange` batch so\n\t\t * the dragged nodes snap, and updates `guides`. Returns the (possibly new) changes array.\n\t\t * A batch without a dragging position change ends the drag and clears the guides.\n\t\t */\n\t\tsnapPositionChanges<TChange extends PositionChangeLike>(changes: TChange[]): TChange[] {\n\t\t\tconst dragging = changes.filter(\n\t\t\t\t(c): c is TChange & { id: string; position: XYPosition } =>\n\t\t\t\t\tc.type === 'position' && typeof c.id === 'string' && !!c.position && !!c.dragging\n\t\t\t);\n\t\t\tif (dragging.length === 0) {\n\t\t\t\tif (!changes.some((c) => c.type === 'position' && c.dragging === false)) return changes;\n\t\t\t\t// React Flow's drag-stop change carries the raw pointer position, which would\n\t\t\t\t// undo the snap of the last frame; keep the snapped position instead.\n\t\t\t\tconst snapped = lastSnapped;\n\t\t\t\tlastSnapped = new Map(); // applied to the changes below, not by endDrag\n\t\t\t\tcontroller.endDrag();\n\t\t\t\tconst keep = (c: TChange) =>\n\t\t\t\t\tc.type === 'position' && c.dragging === false && !!c.position && c.id !== undefined && snapped.has(c.id);\n\t\t\t\tif (!changes.some(keep)) return changes;\n\t\t\t\treturn changes.map((c) => (keep(c) ? { ...c, position: snapped.get(c.id!)! } : c));\n\t\t\t}\n\t\t\tif (options.snapping === false) return changes;\n\t\t\tconst ids = dragging.map((c) => c.id);\n\t\t\tconst key = ids.slice().sort().join('\\u0000');\n\t\t\tif (!dragIndex || key !== dragKey) controller.beginDrag(ids);\n\t\t\tconst proposed = new Map(dragging.map((c) => [c.id, c.position]));\n\t\t\tconst nodes = options.getNodes().map((n) => (proposed.has(n.id) ? { ...n, position: proposed.get(n.id)! } : n));\n\t\t\tconst result = snapNodeDrag(nodes, ids, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: dragIndex!\n\t\t\t});\n\t\t\tsetGuides(result.guides);\n\t\t\tconst { x: dx, y: dy } = result.offset;\n\t\t\tlastSnapped = new Map();\n\t\t\tif (dx === 0 && dy === 0) return changes;\n\t\t\t// Children of a dragged parent move with it; only shift top-level dragged nodes.\n\t\t\tconst parents = new Map(nodes.map((n) => [n.id, n.parentId]));\n\t\t\tconst shift = new Set(ids.filter((id) => !proposed.has(parents.get(id) ?? '')));\n\t\t\treturn changes.map((c) => {\n\t\t\t\tif (!(c.type === 'position' && c.position && c.dragging && c.id !== undefined && shift.has(c.id))) return c;\n\t\t\t\tconst position = { x: c.position.x + dx, y: c.position.y + dy };\n\t\t\t\tlastSnapped.set(c.id, position);\n\t\t\t\treturn { ...c, position };\n\t\t\t});\n\t\t},\n\n\t\t/**\n\t\t * Starts resize snapping for node `id`: records its box (to tell which handle is\n\t\t * dragged) and caches the snap targets. Optional: `resize` starts lazily.\n\t\t */\n\t\tbeginResize(id: string) {\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tconst node = lookup.get(id);\n\t\t\tresizeId = id;\n\t\t\tresizeStart = node ? getResizingNodeRect(node, lookup, rect()) : null;\n\t\t\tresizeIndex = createNodeGuideIndex(nodes, [id], rect());\n\t\t\tlastResized = null;\n\t\t},\n\n\t\t/**\n\t\t * Snaps node `id` as currently sized in `getNodes()` (call it after the flow wrote the\n\t\t * new size, e.g. SvelteFlow's `NodeResizer` `onResize`, deferred by a microtask) and\n\t\t * writes the snapped box back through `setNodes` (not recorded in history). `handle`\n\t\t * defaults to the one inferred from the edges that moved since `beginResize`. A locked\n\t\t * node is restored to its starting box.\n\t\t */\n\t\tresize(id: string, handle?: ResizeHandle): SnapNodeResizeResult | null {\n\t\t\tif (resizeId !== id) controller.beginResize(id);\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tconst node = lookup.get(id);\n\t\t\tif (!node || !resizeStart) return null;\n\t\t\tconst proposed = getResizingNodeRect(node, lookup, rect());\n\t\t\tif (isLocked(node)) {\n\t\t\t\tconst start = resizeStart;\n\t\t\t\tconst parent = node.parentId ? lookup.get(node.parentId) : undefined;\n\t\t\t\tconst origin = parent ? getResizingNodeRect(parent, lookup, rect()) : { x: 0, y: 0 };\n\t\t\t\tif (proposed.x !== start.x || proposed.y !== start.y || proposed.width !== start.width || proposed.height !== start.height) {\n\t\t\t\t\toptions.setNodes(\n\t\t\t\t\t\tapplyNodeUpdates(nodes, [\n\t\t\t\t\t\t\t{ id, position: { x: start.x - origin.x, y: start.y - origin.y }, width: start.width, height: start.height }\n\t\t\t\t\t\t])\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tif ((options.resizeSnapping ?? options.snapping) === false) return null;\n\t\t\tconst h = handle ?? inferResizeHandle(resizeStart, proposed);\n\t\t\tif (!h) {\n\t\t\t\tsetGuides([]);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst result = snapNodeResize(nodes, id, proposed, h, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\t...options.resize,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: resizeIndex!\n\t\t\t});\n\t\t\tlastResized = result.updates.length > 0 ? result : null;\n\t\t\tif (result.updates.length > 0) options.setNodes(applyNodeUpdates(nodes, result.updates));\n\t\t\tsetGuides(result.guides);\n\t\t\treturn result;\n\t\t},\n\n\t\t/**\n\t\t * Ends resize snapping: clears guides and re-applies the last snapped box if the flow\n\t\t * wrote the raw pointer size afterwards.\n\t\t */\n\t\tendResize() {\n\t\t\tconst last = lastResized;\n\t\t\tresizeId = null;\n\t\t\tresizeStart = null;\n\t\t\tresizeIndex = null;\n\t\t\tlastResized = null;\n\t\t\tsetGuides([]);\n\t\t\tconst update = last?.updates[0];\n\t\t\tif (!update) return;\n\t\t\tconst node = options.getNodes().find((n) => n.id === update.id);\n\t\t\tif (!node) return;\n\t\t\tconst drift =\n\t\t\t\tnode.position.x !== update.position!.x ||\n\t\t\t\tnode.position.y !== update.position!.y ||\n\t\t\t\tnode.width !== update.width ||\n\t\t\t\tnode.height !== update.height;\n\t\t\tif (drift) options.setNodes(applyNodeUpdates(options.getNodes(), [update]));\n\t\t},\n\n\t\t/**\n\t\t * React Flow style: snaps the `dimensions` (and matching `position`) changes a\n\t\t * `NodeResizer` emits in an `onNodesChange` batch and updates `guides`. The resize-end\n\t\t * change (`resizing: false`) carries the raw pointer size, so the last snapped size is\n\t\t * kept. Resize changes of locked nodes are dropped. Returns the (possibly new) changes.\n\t\t */\n\t\tsnapResizeChanges<TChange extends PositionChangeLike>(changes: TChange[]): TChange[] {\n\t\t\tconst dims = changes.find(\n\t\t\t\t(c) => c.type === 'dimensions' && typeof c.id === 'string' && !!c.dimensions && typeof c.resizing === 'boolean'\n\t\t\t);\n\t\t\tif (!dims) return changes;\n\t\t\tconst id = dims.id as string;\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tconst node = lookup.get(id);\n\t\t\tconst isResizeChange = (c: TChange) => c.id === id && (c.type === 'dimensions' || c.type === 'position');\n\t\t\tif (node && isLocked(node)) {\n\t\t\t\tif (dims.resizing === false) controller.endResize();\n\t\t\t\treturn changes.filter((c) => !isResizeChange(c));\n\t\t\t}\n\t\t\tif (dims.resizing === false) {\n\t\t\t\tconst last = lastResized?.updates[0];\n\t\t\t\tlastResized = null; // applied to the change below, not by endResize\n\t\t\t\tcontroller.endResize();\n\t\t\t\tif (!last || last.id !== id) return changes;\n\t\t\t\treturn changes.map((c) =>\n\t\t\t\t\tc === dims ? { ...c, dimensions: { width: last.width!, height: last.height! } } : c\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (!node || (options.resizeSnapping ?? options.snapping) === false) return changes;\n\t\t\tif (resizeId !== id) controller.beginResize(id);\n\t\t\tif (!resizeStart) return changes;\n\t\t\tconst posChange = changes.find((c) => c.id === id && c.type === 'position' && !!c.position);\n\t\t\tconst parent = node.parentId ? lookup.get(node.parentId) : undefined;\n\t\t\tconst origin = parent ? getResizingNodeRect(parent, lookup, rect()) : { x: 0, y: 0 };\n\t\t\tconst position = posChange?.position ?? node.position;\n\t\t\tconst proposed: Rect = {\n\t\t\t\tx: position.x + origin.x,\n\t\t\t\ty: position.y + origin.y,\n\t\t\t\twidth: dims.dimensions.width,\n\t\t\t\theight: dims.dimensions.height\n\t\t\t};\n\t\t\tconst handle = inferResizeHandle(resizeStart, proposed);\n\t\t\tif (!handle) {\n\t\t\t\tsetGuides([]);\n\t\t\t\treturn changes;\n\t\t\t}\n\t\t\tconst result = snapNodeResize(nodes, id, proposed, handle, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\t...options.resize,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: resizeIndex!\n\t\t\t});\n\t\t\tsetGuides(result.guides);\n\t\t\tconst update = result.updates[0];\n\t\t\tlastResized = update ? result : null;\n\t\t\tif (!update) return changes;\n\t\t\tconst next = changes.map((c) => {\n\t\t\t\tif (c === dims) return { ...c, dimensions: { width: update.width!, height: update.height! } };\n\t\t\t\tif (c === posChange) return { ...c, position: update.position! };\n\t\t\t\treturn c;\n\t\t\t});\n\t\t\tif (!posChange && (update.position!.x !== node.position.x || update.position!.y !== node.position.y)) {\n\t\t\t\tnext.push({ id, type: 'position', position: update.position! } as unknown as TChange);\n\t\t\t}\n\t\t\treturn next;\n\t\t},\n\n\t\t/**\n\t\t * Clears guides and the cached snap targets. Call it from the flow's drag-stop\n\t\t * handler: flow libraries write the raw pointer position when a drag ends, so the\n\t\t * last snapped positions from `drag` are written back here.\n\t\t */\n\t\tendDrag() {\n\t\t\tconst snapped = lastSnapped;\n\t\t\tdragIndex = null;\n\t\t\tdragKey = '';\n\t\t\tlastSnapped = new Map();\n\t\t\tsetGuides([]);\n\t\t\tif (snapped.size === 0) return;\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst drift = nodes.some((n) => {\n\t\t\t\tconst p = snapped.get(n.id);\n\t\t\t\treturn !!p && (n.position.x !== p.x || n.position.y !== p.y);\n\t\t\t});\n\t\t\tif (drift) {\n\t\t\t\toptions.setNodes(applyNodeUpdates(nodes, [...snapped].map(([id, position]) => ({ id, position }))));\n\t\t\t}\n\t\t}\n\t};\n\treturn controller;\n}\n\nexport type CanvasArrangeController<TNode extends CanvasNode = CanvasNode> = ReturnType<\n\ttypeof createCanvasArrangeController<TNode>\n>;\n","/**\n * Distance measuring between bounding boxes (Alt-hover red lines with value labels).\n *\n * Portions ported from figcraft-main/crates/engine/src/distance.rs (MIT OR Apache-2.0,\n * © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n */\nimport { boundsOf } from './layout.js';\nimport { createNodeLookup, getNodeRect, type NodeRectOptions } from './arrange.js';\nimport type { GuideLine } from './guides.js';\nimport type { CanvasNode, Rect } from './types.js';\n\n/** Lines shorter than this (flow units) are not drawn. */\nconst EPS = 1e-6;\n\n/**\n * A measuring line in flow coordinates. `measure` lines carry a `label` (the distance);\n * `extension` lines are dashed, unlabelled lines that carry a gap over to the measured box\n * when the gap line passes beside it.\n */\nexport interface MeasureGuide extends GuideLine {\n\tkind: 'measure' | 'extension';\n\t/** The distance as shown on the label (`measure` lines only). */\n\tlabel?: string;\n}\n\n/** A distance as shown on a label: absolute value, up to two decimals, no trailing zeros. */\nexport function measureLabel(distance: number): string {\n\tconst v = Math.round(Math.abs(distance) * 100) / 100;\n\treturn Object.is(v, -0) ? '0' : String(v);\n}\n\ninterface Box {\n\tx0: number;\n\ty0: number;\n\tx1: number;\n\ty1: number;\n}\n\nconst toBox = (r: Rect): Box => ({ x0: r.x, y0: r.y, x1: r.x + r.width, y1: r.y + r.height });\nconst finiteBox = (b: Box) => [b.x0, b.y0, b.x1, b.y1].every(Number.isFinite);\n\nfunction line(kind: 'measure' | 'extension', x1: number, y1: number, x2: number, y2: number): MeasureGuide {\n\treturn { orientation: x1 === x2 ? 'vertical' : 'horizontal', kind, x1, y1, x2, y2 };\n}\n\n/** A labelled distance line, unless it has no length. */\nfunction measured(out: MeasureGuide[], x1: number, y1: number, x2: number, y2: number) {\n\tconst d = Math.hypot(x2 - x1, y2 - y1);\n\tif (d > EPS && Number.isFinite(d)) out.push({ ...line('measure', x1, y1, x2, y2), label: measureLabel(d) });\n}\n\n/** A dashed extension line (no label), unless it has no length. */\nfunction extension(out: MeasureGuide[], x1: number, y1: number, x2: number, y2: number) {\n\tif (Math.hypot(x2 - x1, y2 - y1) > EPS) out.push(line('extension', x1, y1, x2, y2));\n}\n\nfunction contains(outer: Box, inner: Box): boolean {\n\treturn outer.x0 <= inner.x0 + EPS && outer.y0 <= inner.y0 + EPS && outer.x1 >= inner.x1 - EPS && outer.y1 >= inner.y1 - EPS;\n}\n\n/** The middle of the overlap of two spans, or `null` when they do not overlap. */\nfunction overlapMid(a0: number, a1: number, b0: number, b1: number): number | null {\n\tconst lo = Math.max(a0, b0);\n\tconst hi = Math.min(a1, b1);\n\treturn lo <= hi ? (lo + hi) / 2 : null;\n}\n\n/** From `inner` to each edge of `outer`, along `inner`'s centre lines. */\nfunction inside(out: MeasureGuide[], inner: Box, outer: Box) {\n\tconst cx = (inner.x0 + inner.x1) / 2;\n\tconst cy = (inner.y0 + inner.y1) / 2;\n\tmeasured(out, outer.x0, cy, inner.x0, cy);\n\tmeasured(out, inner.x1, cy, outer.x1, cy);\n\tmeasured(out, cx, outer.y0, cx, inner.y0);\n\tmeasured(out, cx, inner.y1, cx, outer.y1);\n}\n\n/**\n * Distance lines from box `a` (the selection) to box `b` (the hovered shape):\n *\n * - **One inside the other**: from the inner box to each edge of the outer one, along the\n * inner box's centre lines (the same lines either way round).\n * - **Overlapping**: between the matching edges (left to left, right to right, ...).\n * - **Apart**: the horizontal and/or vertical gap, across the shared rows / columns, or from\n * the middle of `a` with a dashed `extension` line to `b` when the gap line passes beside it.\n * Touching boxes have no gap to draw.\n */\nexport function measureBetween(a: Rect, b: Rect): MeasureGuide[] {\n\tconst out: MeasureGuide[] = [];\n\tconst A = toBox(a);\n\tconst B = toBox(b);\n\tif (!finiteBox(A) || !finiteBox(B)) return out;\n\tif (contains(B, A)) {\n\t\tinside(out, A, B);\n\t\treturn out;\n\t}\n\tif (contains(A, B)) {\n\t\tinside(out, B, A);\n\t\treturn out;\n\t}\n\tconst ox = overlapMid(A.x0, A.x1, B.x0, B.x1);\n\tconst oy = overlapMid(A.y0, A.y1, B.y0, B.y1);\n\tconst overlapping = A.x0 < B.x1 && B.x0 < A.x1 && A.y0 < B.y1 && B.y0 < A.y1;\n\tif (overlapping && ox !== null && oy !== null) {\n\t\tmeasured(out, A.x0, oy, B.x0, oy);\n\t\tmeasured(out, A.x1, oy, B.x1, oy);\n\t\tmeasured(out, ox, A.y0, ox, B.y0);\n\t\tmeasured(out, ox, A.y1, ox, B.y1);\n\t\treturn out;\n\t}\n\tconst cx = (A.x0 + A.x1) / 2;\n\tconst cy = (A.y0 + A.y1) / 2;\n\tconst gapX = B.x0 >= A.x1 ? [A.x1, B.x0] : B.x1 <= A.x0 ? [A.x0, B.x1] : null;\n\tif (gapX) {\n\t\tconst [from, to] = gapX;\n\t\tconst y = oy ?? cy;\n\t\tmeasured(out, from, y, to, y);\n\t\tif (oy === null) extension(out, to, y, to, y < B.y0 ? B.y0 : B.y1);\n\t}\n\tconst gapY = B.y0 >= A.y1 ? [A.y1, B.y0] : B.y1 <= A.y0 ? [A.y0, B.y1] : null;\n\tif (gapY) {\n\t\tconst [from, to] = gapY;\n\t\tconst x = ox ?? cx;\n\t\tmeasured(out, x, from, x, to);\n\t\tif (ox === null) extension(out, x, to, x < B.x0 ? B.x0 : B.x1, to);\n\t}\n\treturn out;\n}\n\n/**\n * Distance lines from box `a` to an infinite guide line (`vertical` at x = `at`, otherwise\n * horizontal at y = `at`): to the nearer side, or to both sides when the line crosses the box.\n */\nexport function measureToLine(a: Rect, orientation: 'vertical' | 'horizontal', at: number): MeasureGuide[] {\n\tconst out: MeasureGuide[] = [];\n\tconst A = toBox(a);\n\tif (!finiteBox(A) || !Number.isFinite(at)) return out;\n\tconst vertical = orientation === 'vertical';\n\tconst cx = (A.x0 + A.x1) / 2;\n\tconst cy = (A.y0 + A.y1) / 2;\n\tconst [lo, hi] = vertical ? [A.x0, A.x1] : [A.y0, A.y1];\n\tconst seg = (from: number, to: number) =>\n\t\tvertical ? measured(out, from, cy, to, cy) : measured(out, cx, from, cx, to);\n\tif (at >= hi) seg(hi, at);\n\telse if (at <= lo) seg(at, lo);\n\telse {\n\t\tseg(lo, at);\n\t\tseg(at, hi);\n\t}\n\treturn out;\n}\n\nexport interface MeasureSelectionResult {\n\t/** Measuring lines in flow coordinates. */\n\tguides: MeasureGuide[];\n\t/** The box measured to (to outline), or `null` when nothing is measured. */\n\ttarget: Rect | null;\n\t/** The selection's bounding box, or `null` without a selection. */\n\tselection: Rect | null;\n}\n\nexport interface MeasureSelectionOptions extends NodeRectOptions {}\n\n/**\n * Alt-hover measuring on canvas nodes: from the bounding box of `selectedIds` (absolute,\n * union of several nodes) to the hovered node. Hovering the selection itself, or nothing\n * (`hoveredId` null), measures to the selection's parent (`parentId`) when it has one;\n * a top-level selection then measures nothing. Hidden nodes are ignored.\n */\nexport function measureSelection(\n\tnodes: CanvasNode[],\n\tselectedIds: Iterable<string>,\n\thoveredId: string | null | undefined,\n\toptions: MeasureSelectionOptions = {}\n): MeasureSelectionResult {\n\tconst none = (selection: Rect | null = null): MeasureSelectionResult => ({ guides: [], target: null, selection });\n\tconst lookup = createNodeLookup(nodes);\n\tconst selected = [...new Set(selectedIds)]\n\t\t.map((id) => lookup.get(id))\n\t\t.filter((n): n is CanvasNode => !!n && n.hidden !== true);\n\tif (selected.length === 0) return none();\n\tconst selection = boundsOf(selected.map((n) => getNodeRect(n, lookup, options)));\n\tif (!selection) return none();\n\tconst selectedSet = new Set(selected.map((n) => n.id));\n\n\tlet targetId = hoveredId ?? null;\n\tif (targetId === null || selectedSet.has(targetId)) {\n\t\tconst parentId = selected[0].parentId;\n\t\tconst sameParent = selected.every((n) => n.parentId === parentId);\n\t\ttargetId = parentId && sameParent && !selectedSet.has(parentId) ? parentId : null;\n\t}\n\tconst target = targetId !== null ? lookup.get(targetId) : undefined;\n\tif (!target || target.hidden === true) return none(selection);\n\tconst rect = getNodeRect(target, lookup, options);\n\treturn { guides: measureBetween(selection, rect), target: rect, selection };\n}\n\n/**\n * Whether the measuring modifier (Alt / Option) is held after a keyboard or pointer event.\n * `keyup` of Alt releases it; `keydown` of Alt or any event with `altKey` holds it.\n */\nexport function isMeasureModifierHeld(\n\tevent: { type?: string; key?: string; altKey?: boolean } | null | undefined\n): boolean {\n\tif (!event) return false;\n\tif (event.type === 'keyup' && event.key === 'Alt') return false;\n\treturn event.altKey === true || (event.type === 'keydown' && event.key === 'Alt');\n}\n","import { applyNodeUpdates, createNodeLookup, getNodeRect, getNodeSize, invertNodeUpdates, type CanvasNodeUpdate } from './arrange.js';\nimport type { ArrangeHistoryTarget, PositionChangeLike } from './arrangeController.js';\nimport {\n\tapplyConstraints,\n\tgetAutoLayoutInsertTarget,\n\tgetLayoutParent,\n\tisAutoLayoutFrame,\n\tlayoutAll,\n\tlayoutUpMany,\n\tmoveLayoutChild,\n\ttype AutoLayoutInsertTarget,\n\ttype AutoLayoutOptions\n} from './autoLayout.js';\nimport type { GuideLine } from './guides.js';\nimport type { CanvasNode, XYPosition } from './types.js';\n\nexport interface CanvasAutoLayoutControllerOptions<TNode extends CanvasNode = CanvasNode> extends AutoLayoutOptions {\n\tgetNodes: () => TNode[];\n\tsetNodes: (nodes: TNode[]) => void;\n\t/** When set, controller actions (add / remove / resize / reorder / apply) are one undoable step each. */\n\thistory?: ArrangeHistoryTarget;\n\t/** Turns automatic relayout off (explicit `applyAutoLayout` still works). Default `true`. */\n\tenabled?: boolean;\n\t/** Reorder flow children by dragging them inside their frame (default `true`). */\n\tdragReorder?: boolean;\n\t/** Called when the drop marker of a reorder drag changes (empty when none). */\n\tonGuidesChange?: (guides: GuideLine[]) => void;\n}\n\n/**\n * Merges the updates of several layout passes (later ones win per field).\n */\nexport function mergeNodeUpdates(...lists: CanvasNodeUpdate[][]): CanvasNodeUpdate[] {\n\tconst byId = new Map<string, CanvasNodeUpdate>();\n\tfor (const list of lists) for (const u of list) byId.set(u.id, { ...byId.get(u.id), ...u });\n\treturn [...byId.values()];\n}\n\n/**\n * Updates that re-run auto layout around `ids` after a change: each id's own layout (if a\n * frame) and its auto layout ancestors, plus the frames that lost removed children\n * (`parentIds`). Pure helper used by the controllers.\n */\nexport function relayoutUpdates(nodes: CanvasNode[], ids: Iterable<string>, options: AutoLayoutOptions = {}): CanvasNodeUpdate[] {\n\treturn layoutUpMany(nodes, ids, options);\n}\n\n/**\n * Framework-agnostic auto layout controller shared by the React hook and the Svelte rune.\n * Applies auto layout to frames (`data.layout`) after nodes inside them are added, removed,\n * resized or reordered, either through its own (undoable) actions or by watching the flow's\n * node changes (`noteChanges` + `flush`), and reorders flow children dragged within a frame.\n */\nexport function createCanvasAutoLayoutController<TNode extends CanvasNode = CanvasNode>(\n\toptions: CanvasAutoLayoutControllerOptions<TNode>\n) {\n\tconst layoutOptions = (): AutoLayoutOptions => ({ measure: options.measure, rectOptions: options.rectOptions });\n\tconst enabled = () => options.enabled !== false;\n\n\t// Changes noted from the flow, applied by `flush`.\n\tconst pendingIds = new Set<string>();\n\tconst pendingResizes = new Map<string, { width: number; height: number }>();\n\tconst resizeStarts = new Map<string, { width: number; height: number }>();\n\tlet pendingAll = false;\n\t// Reorder drags: start position of each dragged flow child, and drops to commit.\n\tconst dragStarts = new Map<string, XYPosition>();\n\tconst pendingDrops = new Set<string>();\n\tlet guides: GuideLine[] = [];\n\n\tconst setGuides = (next: GuideLine[]) => {\n\t\tif (next.length === 0 && guides.length === 0) return;\n\t\tguides = next;\n\t\toptions.onGuidesChange?.(guides);\n\t};\n\n\t/**\n\t * Runs a structural change plus the relayout it causes as one (optionally undoable) step.\n\t * `forward` / `backward` change the array; `ids` are relayout around after `forward`.\n\t */\n\tfunction run(\n\t\tname: string,\n\t\tforward: (nodes: TNode[]) => TNode[],\n\t\tbackward: (nodes: TNode[]) => TNode[],\n\t\tids: (after: TNode[]) => Iterable<string>,\n\t\textra: CanvasNodeUpdate[] = [],\n\t\trestore?: { id: string; position: XYPosition }\n\t) {\n\t\tconst before = options.getNodes();\n\t\tconst structural = forward(before);\n\t\tconst withExtra = applyNodeUpdates(structural, extra);\n\t\tconst layout = enabled() || name === 'Auto Layout' ? relayoutUpdates(withExtra, ids(withExtra), layoutOptions()) : [];\n\t\tconst updates = mergeNodeUpdates(extra, layout);\n\t\tif (structural === before && updates.length === 0) return updates;\n\t\tlet inverse = invertNodeUpdates(structural, updates);\n\t\tif (restore) {\n\t\t\t// Undo puts a dragged node back where the drag began (the flow wrote the drag outside history).\n\t\t\tinverse = mergeNodeUpdates(inverse, [{ id: restore.id, position: { ...restore.position } }]);\n\t\t}\n\t\tconst execute = () => options.setNodes(applyNodeUpdates(forward(options.getNodes()), updates));\n\t\tconst undo = () => options.setNodes(backward(applyNodeUpdates(options.getNodes(), inverse)));\n\t\tif (options.history) options.history.execute({ name, execute, undo });\n\t\telse execute();\n\t\treturn updates;\n\t}\n\n\tconst identity = (nodes: TNode[]) => nodes;\n\n\tconst controller = {\n\t\t/** Drop marker of the active reorder drag (flow coordinates); empty otherwise. */\n\t\tget guides() {\n\t\t\treturn guides;\n\t\t},\n\n\t\t/**\n\t\t * Lays out frame `frameId` and its auto layout ancestors (default: every frame) as one\n\t\t * (optionally undoable) step. Returns the updates.\n\t\t */\n\t\tapplyAutoLayout(frameId?: string): CanvasNodeUpdate[] {\n\t\t\tif (frameId === undefined) {\n\t\t\t\tconst updates = layoutAll(options.getNodes(), layoutOptions());\n\t\t\t\treturn run('Auto Layout', identity, identity, () => [], updates);\n\t\t\t}\n\t\t\treturn run('Auto Layout', identity, identity, () => [frameId]);\n\t\t},\n\n\t\t/** Updates that relayout around `ids` on the current nodes, without applying them. */\n\t\tlayoutUpdates(ids: Iterable<string>): CanvasNodeUpdate[] {\n\t\t\treturn relayoutUpdates(options.getNodes(), ids, layoutOptions());\n\t\t},\n\n\t\t/**\n\t\t * Applies the relayout around `ids` (frames, or nodes inside frames) directly, not\n\t\t * recorded in history: auto layout is derived state, re-run after any change.\n\t\t */\n\t\trelayout(ids: Iterable<string>): CanvasNodeUpdate[] {\n\t\t\tconst updates = relayoutUpdates(options.getNodes(), ids, layoutOptions());\n\t\t\tif (updates.length > 0) options.setNodes(applyNodeUpdates(options.getNodes(), updates));\n\t\t\treturn updates;\n\t\t},\n\n\t\t/**\n\t\t * Adds `node` (into the frame named by its `parentId`, at flow `index` when given) and\n\t\t * lays out its frame, as one step.\n\t\t */\n\t\taddNode(node: TNode, addOptions: { index?: number } = {}): CanvasNodeUpdate[] {\n\t\t\tconst forward = (nodes: TNode[]) => {\n\t\t\t\tif (nodes.some((n) => n.id === node.id)) return nodes;\n\t\t\t\tlet next = [...nodes, node];\n\t\t\t\tif (addOptions.index !== undefined && node.parentId) next = moveLayoutChild(next, node.id, addOptions.index);\n\t\t\t\treturn next;\n\t\t\t};\n\t\t\tconst backward = (nodes: TNode[]) => nodes.filter((n) => n.id !== node.id);\n\t\t\treturn run('Add Node', forward, backward, () => [node.id]);\n\t\t},\n\n\t\t/** Removes nodes (and their descendants) and lays out the frames they leave, as one step. */\n\t\tremoveNodes(ids: Iterable<string>): CanvasNodeUpdate[] {\n\t\t\tconst before = options.getNodes();\n\t\t\tconst remove = new Set(ids);\n\t\t\t// Descendants go too (xyflow cannot render orphans).\n\t\t\tlet grew = true;\n\t\t\twhile (grew) {\n\t\t\t\tgrew = false;\n\t\t\t\tfor (const n of before) {\n\t\t\t\t\tif (n.parentId && remove.has(n.parentId) && !remove.has(n.id)) {\n\t\t\t\t\t\tremove.add(n.id);\n\t\t\t\t\t\tgrew = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst removed = before.map((n, i) => [i, n] as const).filter(([, n]) => remove.has(n.id));\n\t\t\tif (removed.length === 0) return [];\n\t\t\tconst parents = [...new Set(removed.map(([, n]) => n.parentId).filter((p): p is string => !!p && !remove.has(p)))];\n\t\t\tconst forward = (nodes: TNode[]) => nodes.filter((n) => !remove.has(n.id));\n\t\t\tconst backward = (nodes: TNode[]) => {\n\t\t\t\tconst next = [...nodes];\n\t\t\t\tfor (const [i, n] of removed) next.splice(Math.min(i, next.length), 0, n);\n\t\t\t\treturn next;\n\t\t\t};\n\t\t\treturn run('Delete', forward, backward, () => parents);\n\t\t},\n\n\t\t/**\n\t\t * Resizes node `id` (its children follow their constraints) and lays out around it,\n\t\t * as one step.\n\t\t */\n\t\tresizeNode(id: string, size: { width?: number; height?: number }): CanvasNodeUpdate[] {\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst node = nodes.find((n) => n.id === id);\n\t\t\tif (!node) return [];\n\t\t\tconst old = getNodeSize(node, options.rectOptions);\n\t\t\tconst update: CanvasNodeUpdate = { id };\n\t\t\tif (size.width !== undefined) update.width = size.width;\n\t\t\tif (size.height !== undefined) update.height = size.height;\n\t\t\tconst resized = applyNodeUpdates(nodes, [update]);\n\t\t\tconst constrained = mergeNodeUpdates([update], applyConstraints(resized, id, old, layoutOptions()));\n\t\t\treturn run('Resize', identity, identity, () => [id], constrained);\n\t\t},\n\n\t\t/** Moves flow child `childId` to flow index `index` in its frame and lays it out, as one step. */\n\t\tmoveChild(childId: string, index: number): CanvasNodeUpdate[] {\n\t\t\tconst before = options.getNodes();\n\t\t\tconst order = before.map((n) => n.id);\n\t\t\tconst forward = (nodes: TNode[]) => moveLayoutChild(nodes, childId, index);\n\t\t\tconst backward = (nodes: TNode[]) => restoreOrder(nodes, order);\n\t\t\tif (forward(before) === before) return [];\n\t\t\treturn run('Reorder', forward, backward, () => [childId]);\n\t\t},\n\n\t\t/** Where flow child `id` would drop if released at `point` (default: its centre). */\n\t\tgetInsertTarget(id: string, point?: XYPosition): AutoLayoutInsertTarget | undefined {\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst frame = getLayoutParent(nodes, id);\n\t\t\tif (!frame) return undefined;\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tconst rect = getNodeRect(lookup.get(id)!, lookup, options.rectOptions);\n\t\t\tconst at = point ?? { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };\n\t\t\treturn getAutoLayoutInsertTarget(nodes, frame.id, at, { ...layoutOptions(), excludeId: id });\n\t\t},\n\n\t\t/** Records the start position of a reorder drag. Optional: `dragReorder` records it lazily. */\n\t\tbeginReorderDrag(id: string) {\n\t\t\tconst node = options.getNodes().find((n) => n.id === id);\n\t\t\tif (node && getLayoutParent(options.getNodes(), id)) dragStarts.set(id, { ...node.position });\n\t\t},\n\n\t\t/**\n\t\t * While flow child `id` is dragged (as currently positioned in `getNodes()`): updates the\n\t\t * drop marker. Returns the insert target.\n\t\t */\n\t\tdragReorder(id: string, point?: XYPosition): AutoLayoutInsertTarget | undefined {\n\t\t\tif (options.dragReorder === false) return undefined;\n\t\t\tif (!dragStarts.has(id)) controller.beginReorderDrag(id);\n\t\t\tconst target = controller.getInsertTarget(id, point);\n\t\t\tsetGuides(target ? [target.guide] : []);\n\t\t\treturn target;\n\t\t},\n\n\t\t/**\n\t\t * Ends a reorder drag: moves `id` to the slot under `point` (default: its centre) and lays\n\t\t * the frame out, as one undoable step whose undo puts the node back where the drag began.\n\t\t */\n\t\tendReorderDrag(id: string, point?: XYPosition): CanvasNodeUpdate[] {\n\t\t\tsetGuides([]);\n\t\t\tconst start = dragStarts.get(id);\n\t\t\tdragStarts.delete(id);\n\t\t\tif (options.dragReorder === false || !getLayoutParent(options.getNodes(), id)) return [];\n\t\t\tconst target = controller.getInsertTarget(id, point);\n\t\t\tif (!target) return [];\n\t\t\tconst before = options.getNodes();\n\t\t\tconst order = before.map((n) => n.id);\n\t\t\tconst forward = (nodes: TNode[]) => moveLayoutChild(nodes, id, target.index);\n\t\t\tconst backward = (nodes: TNode[]) => restoreOrder(nodes, order);\n\t\t\treturn run('Reorder', forward, backward, () => [id], [], start && { id, position: start });\n\t\t},\n\n\t\t/**\n\t\t * Notes a batch of React Flow / SvelteFlow style node changes (before or after they are\n\t\t * applied) so `flush` can relayout: dimension changes (measured sizes, resize ends),\n\t\t * added and removed nodes, and position changes of flow children (reorder drags).\n\t\t * Returns the changes unchanged.\n\t\t */\n\t\tnoteChanges<TChange extends PositionChangeLike>(changes: TChange[]): TChange[] {\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst lookup = createNodeLookup(nodes);\n\t\t\tfor (const c of changes) {\n\t\t\t\tconst id = c.id ?? c.item?.id;\n\t\t\t\tif (c.type === 'add' && c.item) {\n\t\t\t\t\tpendingIds.add(c.item.id);\n\t\t\t\t} else if (c.type === 'remove' && id) {\n\t\t\t\t\tconst parent = lookup.get(id)?.parentId;\n\t\t\t\t\tif (parent) pendingIds.add(parent);\n\t\t\t\t\telse pendingAll = true;\n\t\t\t\t} else if (c.type === 'replace' && id) {\n\t\t\t\t\tpendingIds.add(id);\n\t\t\t\t} else if (c.type === 'dimensions' && id) {\n\t\t\t\t\tconst node = lookup.get(id);\n\t\t\t\t\tif (c.resizing === true) {\n\t\t\t\t\t\tif (node && !resizeStarts.has(id)) resizeStarts.set(id, getNodeSize(node, options.rectOptions));\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tconst start = resizeStarts.get(id);\n\t\t\t\t\tresizeStarts.delete(id);\n\t\t\t\t\tif (start) pendingResizes.set(id, start);\n\t\t\t\t\tpendingIds.add(id);\n\t\t\t\t} else if (c.type === 'position' && id && options.dragReorder !== false) {\n\t\t\t\t\tif (!getLayoutParent(nodes, id)) continue;\n\t\t\t\t\tif (c.dragging === true) {\n\t\t\t\t\t\tif (!dragStarts.has(id)) {\n\t\t\t\t\t\t\tconst node = lookup.get(id);\n\t\t\t\t\t\t\tif (node) dragStarts.set(id, { ...node.position });\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (c.dragging === false || dragStarts.has(id)) {\n\t\t\t\t\t\tpendingDrops.add(id);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn changes;\n\t\t},\n\n\t\t/**\n\t\t * Notes nodes that were already removed (e.g. SvelteFlow's `ondelete`), so `flush`\n\t\t * relayouts the frames they left. The nodes must still carry their `parentId`.\n\t\t */\n\t\tnoteRemoved(removed: Iterable<{ id: string; parentId?: string }>) {\n\t\t\tconst gone = new Set<string>();\n\t\t\tconst list = [...removed];\n\t\t\tfor (const n of list) gone.add(n.id);\n\t\t\tfor (const n of list) if (n.parentId && !gone.has(n.parentId)) pendingIds.add(n.parentId);\n\t\t},\n\n\t\t/** Whether `noteChanges` left work for `flush`. */\n\t\tget hasPending() {\n\t\t\treturn pendingAll || pendingIds.size > 0 || pendingResizes.size > 0 || pendingDrops.size > 0 || dragStarts.size > 0;\n\t\t},\n\n\t\t/**\n\t\t * Applies the work noted by `noteChanges` to the current nodes: drop markers while\n\t\t * dragging, committed reorders on drop (undoable), and a relayout (not recorded) for\n\t\t * size, add and remove changes. Call it once the changes are applied.\n\t\t */\n\t\tflush(): CanvasNodeUpdate[] {\n\t\t\tlet applied: CanvasNodeUpdate[] = [];\n\t\t\tfor (const id of pendingDrops) applied = mergeNodeUpdates(applied, controller.endReorderDrag(id));\n\t\t\tpendingDrops.clear();\n\t\t\tfor (const id of dragStarts.keys()) controller.dragReorder(id);\n\t\t\tif (!enabled()) {\n\t\t\t\tpendingIds.clear();\n\t\t\t\tpendingResizes.clear();\n\t\t\t\tpendingAll = false;\n\t\t\t\treturn applied;\n\t\t\t}\n\t\t\tlet nodes = options.getNodes();\n\t\t\tlet updates: CanvasNodeUpdate[] = [];\n\t\t\tfor (const [id, previous] of pendingResizes) {\n\t\t\t\tif (!nodes.some((n) => n.id === id)) continue;\n\t\t\t\tconst constrained = applyConstraints(nodes, id, previous, layoutOptions());\n\t\t\t\tnodes = applyNodeUpdates(nodes, constrained);\n\t\t\t\tupdates = mergeNodeUpdates(updates, constrained);\n\t\t\t}\n\t\t\tconst layout = pendingAll\n\t\t\t\t? layoutAll(nodes, layoutOptions())\n\t\t\t\t: relayoutUpdates(nodes, [...pendingIds].filter((id) => nodes.some((n) => n.id === id)), layoutOptions());\n\t\t\tupdates = mergeNodeUpdates(updates, layout);\n\t\t\tpendingIds.clear();\n\t\t\tpendingResizes.clear();\n\t\t\tpendingAll = false;\n\t\t\tif (updates.length > 0) options.setNodes(applyNodeUpdates(options.getNodes(), updates));\n\t\t\treturn mergeNodeUpdates(applied, updates);\n\t\t},\n\n\t\t/** Whether `id` is an auto layout frame. */\n\t\tisFrame: (id: string) => isAutoLayoutFrame(options.getNodes().find((n) => n.id === id))\n\t};\n\n\treturn controller;\n}\n\n/** Reorders `nodes` to follow `order` (ids); ids not in `order` keep their relative place at the end. */\nfunction restoreOrder<TNode extends CanvasNode>(nodes: TNode[], order: string[]): TNode[] {\n\tconst rank = new Map(order.map((id, i) => [id, i]));\n\tconst indexed = nodes.map((n, i) => ({ n, r: rank.get(n.id) ?? order.length + i }));\n\tindexed.sort((a, b) => a.r - b.r);\n\treturn indexed.every((e, i) => e.n === nodes[i]) ? nodes : indexed.map((e) => e.n);\n}\n\nexport type CanvasAutoLayoutController<TNode extends CanvasNode = CanvasNode> = ReturnType<\n\ttypeof createCanvasAutoLayoutController<TNode>\n>;\n","// Portions ported from figcraft/crates/doc/src/lib.rs and figcraft/crates/engine/src/cmd/variables.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport { AUTO_LAYOUT_DATA_KEY, getAutoLayout, resolvePadding } from './autoLayout.js';\nimport { isShapeDescription } from './shapeGeometry.js';\nimport type { CanvasNode, CanvasNodeStyle } from './types.js';\nimport {\n\tTokenError,\n\tgetTokenCollection,\n\tgetTokenVariable,\n\tisTokenInScope,\n\tresolveToken,\n\ttype TokenLiteral,\n\ttype TokenModeContext,\n\ttype TokenResolution,\n\ttype TokenScope,\n\ttype TokenState,\n\ttype TokenType,\n\ttype TokenVariable\n} from '../tokens/index.js';\n\n/**\n * Design-token bindings on canvas nodes.\n *\n * - `node.data.tokenBindings` maps a property key (`fill`, `opacity`, `gap`, …) to a variable id.\n * - `node.data.tokenModes` maps a collection id to a mode id: an explicit mode for the node and\n * its descendants (via `parentId`); the nearest ancestor wins, then the root `modes` option,\n * then the collection's default mode.\n *\n * Bound properties are *materialised*: `resolveCanvasTokens` writes the resolved values into\n * the ordinary node fields (`data.shape.fill`, `style.backgroundColor`, `data.layout.gap`, …), so\n * renderers, auto layout and exports need no token awareness, and a node whose variable is\n * gone keeps its last value. Both keys live in `data`, so they survive copy / paste,\n * duplication and serialisation unchanged.\n */\n\n/** Data key holding a node's property → variable id bindings. */\nexport const TOKEN_BINDINGS_DATA_KEY = 'tokenBindings';\n/** Data key holding a node's explicit collection → mode choices. */\nexport const TOKEN_MODES_DATA_KEY = 'tokenModes';\n\n/** A node property a variable can drive. */\nexport interface CanvasTokenProperty {\n\tkey: string;\n\tlabel: string;\n\t/** The variable type it takes. */\n\ttype: TokenType;\n\t/** The scope pickers filter by (variables scoped elsewhere are not offered). */\n\tscope?: TokenScope | ((node: CanvasNode) => TokenScope | undefined);\n\tappliesTo(node: CanvasNode): boolean;\n\tread(node: CanvasNode): TokenLiteral | undefined;\n\t/** Returns the node with the value written, or the same node when nothing changes. */\n\twrite(node: CanvasNode, value: TokenLiteral): CanvasNode;\n}\n\n// ---------------------------------------------------------------------------------------------\n// Style helpers (CSS string for SvelteFlow, object for React Flow)\n\nconst kebab = (prop: string) => prop.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);\n\n/** Splits a style string at top-level `;` (not inside quotes or parentheses, e.g. data URLs). */\nfunction parseDeclarations(style: string): [string, string][] {\n\tconst parts: string[] = [];\n\tlet depth = 0;\n\tlet quote = '';\n\tlet start = 0;\n\tfor (let i = 0; i < style.length; i++) {\n\t\tconst ch = style[i];\n\t\tif (quote) {\n\t\t\tif (ch === '\\\\') i++;\n\t\t\telse if (ch === quote) quote = '';\n\t\t} else if (ch === '\"' || ch === \"'\") quote = ch;\n\t\telse if (ch === '(') depth++;\n\t\telse if (ch === ')') depth = Math.max(0, depth - 1);\n\t\telse if (ch === ';' && depth === 0) {\n\t\t\tparts.push(style.slice(start, i));\n\t\t\tstart = i + 1;\n\t\t}\n\t}\n\tparts.push(style.slice(start));\n\tconst out: [string, string][] = [];\n\tfor (const part of parts) {\n\t\tconst colon = part.indexOf(':');\n\t\tif (colon < 0) continue;\n\t\tconst name = part.slice(0, colon).trim();\n\t\tif (name) out.push([name, part.slice(colon + 1).trim()]);\n\t}\n\treturn out;\n}\n\n/** The top-level `name: value` declarations of a style string (names as written, in order). */\nexport function parseNodeStyleDeclarations(style: string): [string, string][] {\n\treturn parseDeclarations(style);\n}\n\n/** A style property (camelCase name) from a style string or object. */\nexport function readNodeStyleProperty(style: CanvasNodeStyle | undefined | null, prop: string): string | number | undefined {\n\tif (!style) return undefined;\n\tif (typeof style === 'object') return style[prop];\n\tconst name = kebab(prop);\n\tlet found: string | undefined;\n\tfor (const [key, value] of parseDeclarations(style)) if (key.toLowerCase() === name) found = value;\n\treturn found;\n}\n\n/**\n * Returns `style` with one property set (or removed with `undefined`), keeping its\n * representation. Numbers become `px` in style strings unless `unitless`.\n */\nexport function writeNodeStyleProperty(\n\tstyle: CanvasNodeStyle | undefined | null,\n\tprop: string,\n\tvalue: string | number | undefined,\n\tunitless = false\n): CanvasNodeStyle | undefined {\n\tif (typeof style === 'string') {\n\t\tconst name = kebab(prop);\n\t\tconst text = value === undefined ? undefined : typeof value === 'number' && !unitless ? `${value}px` : String(value);\n\t\t// Replace in place (keeping declaration order), drop duplicates, or append.\n\t\tconst decls: [string, string][] = [];\n\t\tlet placed = text === undefined;\n\t\tfor (const [key, current] of parseDeclarations(style)) {\n\t\t\tif (key.toLowerCase() !== name) decls.push([key, current]);\n\t\t\telse if (!placed) {\n\t\t\t\tdecls.push([name, text!]);\n\t\t\t\tplaced = true;\n\t\t\t}\n\t\t}\n\t\tif (!placed) decls.push([name, text!]);\n\t\treturn decls.map(([k, v]) => `${k}: ${v}`).join('; ');\n\t}\n\tconst next: Record<string, any> = { ...(style ?? {}) };\n\tif (value === undefined) delete next[prop];\n\telse next[prop] = value;\n\treturn next;\n}\n\nfunction px(value: unknown): number | undefined {\n\tif (typeof value === 'number') return Number.isFinite(value) ? value : undefined;\n\tif (typeof value === 'string') {\n\t\tconst n = parseFloat(value);\n\t\treturn Number.isFinite(n) ? n : undefined;\n\t}\n\treturn undefined;\n}\n\nfunction withStyle(node: CanvasNode, prop: string, value: string | number, unitless = false): CanvasNode {\n\tconst current = readNodeStyleProperty(node.style, prop);\n\tconst written = typeof node.style === 'string' && typeof value === 'number' && !unitless ? `${value}px` : value;\n\tif (typeof node.style === 'string' ? current === String(written) : current === value) return node;\n\treturn { ...node, style: writeNodeStyleProperty(node.style, prop, value, unitless) };\n}\n\nfunction shapeOf(node: CanvasNode): Record<string, any> | null {\n\tconst shape = node.data?.shape;\n\treturn isShapeDescription(shape) ? (shape as Record<string, any>) : null;\n}\n\nfunction withShape(node: CanvasNode, key: string, value: unknown): CanvasNode {\n\tconst shape = shapeOf(node)!;\n\tif (shape[key] === value) return node;\n\treturn { ...node, data: { ...node.data, shape: { ...shape, [key]: value } } };\n}\n\nconst nonNegative = (v: TokenLiteral) => Math.max(0, v as number);\n\nfunction styleOrShape(key: 'fill' | 'stroke' | 'strokeWidth', styleProp: (node: CanvasNode) => string, convert: (v: TokenLiteral) => TokenLiteral = (v) => v) {\n\treturn {\n\t\tread(node: CanvasNode): TokenLiteral | undefined {\n\t\t\tconst shape = shapeOf(node);\n\t\t\tif (shape) return shape[key];\n\t\t\tconst value = readNodeStyleProperty(node.style, styleProp(node));\n\t\t\treturn key === 'strokeWidth' ? px(value) : (value as TokenLiteral | undefined);\n\t\t},\n\t\twrite(node: CanvasNode, value: TokenLiteral): CanvasNode {\n\t\t\tconst v = convert(value);\n\t\t\tif (shapeOf(node)) return withShape(node, key, v);\n\t\t\tlet next = withStyle(node, styleProp(node), v as string | number);\n\t\t\tif (key === 'strokeWidth' && readNodeStyleProperty(next.style, 'borderStyle') === undefined) {\n\t\t\t\tnext = { ...next, style: writeNodeStyleProperty(next.style, 'borderStyle', 'solid') };\n\t\t\t}\n\t\t\treturn next;\n\t\t}\n\t};\n}\n\nfunction backgroundProp(node: CanvasNode): string {\n\treturn readNodeStyleProperty(node.style, 'backgroundColor') === undefined && readNodeStyleProperty(node.style, 'background') !== undefined\n\t\t? 'background'\n\t\t: 'backgroundColor';\n}\n\nfunction layoutProperty(key: string, label: string, read: (l: Record<string, any>) => number | undefined, write: (l: Record<string, any>, v: number) => Record<string, any>): CanvasTokenProperty {\n\treturn {\n\t\tkey,\n\t\tlabel,\n\t\ttype: 'number',\n\t\tscope: 'GAP',\n\t\tappliesTo: (node) => getAutoLayout(node) !== undefined,\n\t\tread: (node) => {\n\t\t\tconst layout = getAutoLayout(node);\n\t\t\treturn layout ? read(layout as Record<string, any>) : undefined;\n\t\t},\n\t\twrite: (node, value) => {\n\t\t\tconst layout = getAutoLayout(node) as Record<string, any> | undefined;\n\t\t\tif (!layout || read(layout) === value) return node;\n\t\t\treturn { ...node, data: { ...node.data, [AUTO_LAYOUT_DATA_KEY]: write(layout, value as number) } };\n\t\t}\n\t};\n}\n\ntype Side = 'top' | 'right' | 'bottom' | 'left';\nconst SIDES: Side[] = ['top', 'right', 'bottom', 'left'];\n\nfunction paddingProperty(key: string, label: string, sides: Side[]): CanvasTokenProperty {\n\treturn layoutProperty(\n\t\tkey,\n\t\tlabel,\n\t\t(layout) => {\n\t\t\tconst p = resolvePadding(layout.padding);\n\t\t\tconst values = sides.map((s) => p[SIDES.indexOf(s)]);\n\t\t\treturn values.every((v) => v === values[0]) ? values[0] : undefined;\n\t\t},\n\t\t(layout, value) => {\n\t\t\tconst p = resolvePadding(layout.padding);\n\t\t\tconst v = Math.max(0, value);\n\t\t\tconst padding = { top: p[0], right: p[1], bottom: p[2], left: p[3] };\n\t\t\tfor (const s of sides) padding[s] = v;\n\t\t\treturn { ...layout, padding };\n\t\t}\n\t);\n}\n\n/** The built-in bindable properties. */\nexport const CANVAS_TOKEN_PROPERTIES: readonly CanvasTokenProperty[] = [\n\t{\n\t\tkey: 'fill',\n\t\tlabel: 'Fill',\n\t\ttype: 'color',\n\t\tscope: (node) => (shapeOf(node) ? 'SHAPE_FILL' : node.type === 'text' ? 'TEXT_FILL' : 'FRAME_FILL'),\n\t\tappliesTo: () => true,\n\t\t...styleOrShape('fill', backgroundProp)\n\t},\n\t{\n\t\tkey: 'stroke',\n\t\tlabel: 'Stroke',\n\t\ttype: 'color',\n\t\tscope: 'STROKE_COLOR',\n\t\tappliesTo: () => true,\n\t\t...styleOrShape('stroke', () => 'borderColor')\n\t},\n\t{\n\t\tkey: 'strokeWidth',\n\t\tlabel: 'Stroke width',\n\t\ttype: 'number',\n\t\tscope: 'STROKE_FLOAT',\n\t\tappliesTo: () => true,\n\t\t...styleOrShape('strokeWidth', () => 'borderWidth', nonNegative)\n\t},\n\t{\n\t\tkey: 'opacity',\n\t\tlabel: 'Opacity',\n\t\ttype: 'number',\n\t\tscope: 'OPACITY',\n\t\tappliesTo: () => true,\n\t\t// Opacity variables are percentages (figcraft / Figma): 40 → 0.4, clamped to 0–100.\n\t\tread: (node) => {\n\t\t\tconst v = px(readNodeStyleProperty(node.style, 'opacity'));\n\t\t\treturn v === undefined ? undefined : Math.round(v * 10000) / 100;\n\t\t},\n\t\twrite: (node, value) => withStyle(node, 'opacity', Math.min(1, Math.max(0, (value as number) / 100)), true)\n\t},\n\t{\n\t\tkey: 'cornerRadius',\n\t\tlabel: 'Corner radius',\n\t\ttype: 'number',\n\t\tscope: 'CORNER_RADIUS',\n\t\tappliesTo: (node) => {\n\t\t\tconst shape = shapeOf(node);\n\t\t\treturn !shape || shape.kind === 'rect';\n\t\t},\n\t\tread: (node) => {\n\t\t\tconst shape = shapeOf(node);\n\t\t\tif (shape) return typeof shape.cornerRadius === 'number' ? shape.cornerRadius : undefined;\n\t\t\treturn px(readNodeStyleProperty(node.style, 'borderRadius'));\n\t\t},\n\t\twrite: (node, value) => {\n\t\t\tconst v = Math.max(0, value as number);\n\t\t\treturn shapeOf(node) ? withShape(node, 'cornerRadius', v) : withStyle(node, 'borderRadius', v);\n\t\t}\n\t},\n\tlayoutProperty('gap', 'Gap', (l) => (typeof l.gap === 'number' ? l.gap : 0), (l, v) => ({ ...l, gap: v })),\n\tlayoutProperty('rowGap', 'Row gap', (l) => (typeof l.rowGap === 'number' ? l.rowGap : undefined), (l, v) => ({ ...l, rowGap: v })),\n\tpaddingProperty('padding', 'Padding', ['top', 'right', 'bottom', 'left']),\n\tpaddingProperty('paddingHorizontal', 'Horizontal padding', ['left', 'right']),\n\tpaddingProperty('paddingVertical', 'Vertical padding', ['top', 'bottom']),\n\tpaddingProperty('paddingTop', 'Top padding', ['top']),\n\tpaddingProperty('paddingRight', 'Right padding', ['right']),\n\tpaddingProperty('paddingBottom', 'Bottom padding', ['bottom']),\n\tpaddingProperty('paddingLeft', 'Left padding', ['left']),\n\t{\n\t\tkey: 'textColor',\n\t\tlabel: 'Text colour',\n\t\ttype: 'color',\n\t\tscope: 'TEXT_FILL',\n\t\tappliesTo: (node) => !shapeOf(node),\n\t\tread: (node) => readNodeStyleProperty(node.style, 'color') as string | undefined,\n\t\twrite: (node, value) => withStyle(node, 'color', value as string)\n\t},\n\t{\n\t\tkey: 'fontSize',\n\t\tlabel: 'Font size',\n\t\ttype: 'number',\n\t\tscope: 'FONT_SIZE',\n\t\tappliesTo: (node) => !shapeOf(node),\n\t\tread: (node) => px(readNodeStyleProperty(node.style, 'fontSize')),\n\t\twrite: (node, value) => withStyle(node, 'fontSize', Math.max(0, value as number))\n\t},\n\t{\n\t\tkey: 'visible',\n\t\tlabel: 'Visible',\n\t\ttype: 'boolean',\n\t\tappliesTo: () => true,\n\t\tread: (node) => !node.hidden,\n\t\twrite: (node, value) => (!node.hidden === value ? node : { ...node, hidden: !value })\n\t}\n];\n\nexport interface CanvasTokenOptions {\n\t/** Modes for nodes without an explicit mode up their tree (e.g. the page's). */\n\tmodes?: TokenModeContext;\n\t/** Bindable properties (default {@link CANVAS_TOKEN_PROPERTIES}); add your own to extend. */\n\tproperties?: readonly CanvasTokenProperty[];\n}\n\nconst EMPTY: Readonly<Record<string, string>> = Object.freeze({});\n\nfunction stringRecord(value: unknown): Readonly<Record<string, string>> {\n\tif (typeof value !== 'object' || value === null || Array.isArray(value)) return EMPTY;\n\tconst out: Record<string, string> = {};\n\tfor (const [k, v] of Object.entries(value)) if (typeof v === 'string') out[k] = v;\n\treturn out;\n}\n\n/** The node's property → variable id bindings. */\nexport function getNodeTokenBindings(node: CanvasNode | undefined | null): Readonly<Record<string, string>> {\n\treturn stringRecord(node?.data?.[TOKEN_BINDINGS_DATA_KEY]);\n}\n\n/** The node's own explicit modes (collection id → mode id). */\nexport function getNodeTokenModes(node: CanvasNode | undefined | null): Readonly<Record<string, string>> {\n\treturn stringRecord(node?.data?.[TOKEN_MODES_DATA_KEY]);\n}\n\nexport function getCanvasTokenProperty(key: string, options: CanvasTokenOptions = {}): CanvasTokenProperty | undefined {\n\treturn (options.properties ?? CANVAS_TOKEN_PROPERTIES).find((p) => p.key === key);\n}\n\n/** Properties a variable can be bound to on this node. */\nexport function getBindableTokenProperties(node: CanvasNode, options: CanvasTokenOptions = {}): CanvasTokenProperty[] {\n\treturn (options.properties ?? CANVAS_TOKEN_PROPERTIES).filter((p) => p.appliesTo(node));\n}\n\n/** The scope of a property on a node. */\nexport function getTokenPropertyScope(property: CanvasTokenProperty, node: CanvasNode): TokenScope | undefined {\n\treturn typeof property.scope === 'function' ? property.scope(node) : property.scope;\n}\n\n/** Variables a picker offers for `property` on `node`: the property's type, in scope. */\nexport function getTokensForProperty(state: TokenState, property: CanvasTokenProperty, node: CanvasNode): TokenVariable[] {\n\tconst scope = getTokenPropertyScope(property, node);\n\treturn state.variables.filter((v) => v.type === property.type && isTokenInScope(v, scope));\n}\n\nfunction lookupOf(nodes: readonly CanvasNode[]): Map<string, CanvasNode> {\n\treturn new Map(nodes.map((n) => [n.id, n]));\n}\n\nfunction validModes(state: TokenState | undefined, modes: Readonly<Record<string, string>>, into: Record<string, string>): void {\n\tfor (const [collectionId, modeId] of Object.entries(modes)) {\n\t\tif (state) {\n\t\t\tconst collection = getTokenCollection(state, collectionId);\n\t\t\t// An unknown collection or mode counts as \"Auto\" (figcraft resets them to Auto).\n\t\t\tif (!collection || !collection.modes.some((m) => m.id === modeId)) continue;\n\t\t}\n\t\tinto[collectionId] = modeId;\n\t}\n}\n\n/**\n * The modes `node` is in: the root `modes`, overridden by each ancestor's explicit modes from\n * the top down, then the node's own. Modes `state` doesn't know are ignored.\n */\nexport function getEffectiveTokenModes(\n\tnode: CanvasNode,\n\tnodes: readonly CanvasNode[] | ReadonlyMap<string, CanvasNode>,\n\tstate?: TokenState,\n\toptions: Pick<CanvasTokenOptions, 'modes'> = {}\n): Record<string, string> {\n\tconst lookup = nodes instanceof Map ? nodes : lookupOf(nodes as readonly CanvasNode[]);\n\tconst chain: CanvasNode[] = [];\n\tconst seen = new Set<string>();\n\tfor (let at: CanvasNode | undefined = node; at && !seen.has(at.id); at = at.parentId ? lookup.get(at.parentId) : undefined) {\n\t\tseen.add(at.id);\n\t\tchain.push(at);\n\t}\n\tconst out: Record<string, string> = {};\n\tif (options.modes) validModes(state, options.modes, out);\n\tfor (let i = chain.length - 1; i >= 0; i--) validModes(state, getNodeTokenModes(chain[i]), out);\n\treturn out;\n}\n\nfunction modesResolver(nodes: readonly CanvasNode[], state: TokenState, options: CanvasTokenOptions) {\n\tconst lookup = lookupOf(nodes);\n\tconst memo = new Map<string, Record<string, string>>();\n\tconst root: Record<string, string> = {};\n\tif (options.modes) validModes(state, options.modes, root);\n\tconst visiting = new Set<string>();\n\tconst modesOf = (node: CanvasNode): Record<string, string> => {\n\t\tconst cached = memo.get(node.id);\n\t\tif (cached) return cached;\n\t\tlet base = root;\n\t\tif (node.parentId && !visiting.has(node.id)) {\n\t\t\tconst parent = lookup.get(node.parentId);\n\t\t\tif (parent) {\n\t\t\t\tvisiting.add(node.id);\n\t\t\t\tbase = modesOf(parent);\n\t\t\t\tvisiting.delete(node.id);\n\t\t\t}\n\t\t}\n\t\tconst own = getNodeTokenModes(node);\n\t\tlet result = base;\n\t\tif (Object.keys(own).length > 0) {\n\t\t\tresult = { ...base };\n\t\t\tvalidModes(state, own, result);\n\t\t}\n\t\tmemo.set(node.id, result);\n\t\treturn result;\n\t};\n\treturn { lookup, modesOf };\n}\n\nfunction applyBindings(node: CanvasNode, state: TokenState, modes: TokenModeContext, options: CanvasTokenOptions, only?: ReadonlySet<string>): CanvasNode {\n\tconst bindings = getNodeTokenBindings(node);\n\tlet next = node;\n\tfor (const [key, variableId] of Object.entries(bindings)) {\n\t\tif (only && !only.has(variableId)) continue;\n\t\tconst property = getCanvasTokenProperty(key, options);\n\t\tif (!property || !property.appliesTo(next)) continue;\n\t\tconst resolved = resolveToken(state, variableId, modes);\n\t\tif (!resolved.ok || resolved.type !== property.type) continue;\n\t\tnext = property.write(next, resolved.value);\n\t}\n\treturn next;\n}\n\n/**\n * Writes every bound property's resolved value into its node. Returns the same array when\n * nothing changed (unchanged nodes keep their identity), so it is safe to run on every change.\n * Bindings that don't resolve (deleted variable, cycle) keep the node's current value.\n */\nexport function resolveCanvasTokens<TNode extends CanvasNode>(nodes: readonly TNode[], state: TokenState, options: CanvasTokenOptions = {}): TNode[] {\n\tconst { modesOf } = modesResolver(nodes, state, options);\n\tlet changed = false;\n\tconst out = nodes.map((node) => {\n\t\tif (Object.keys(getNodeTokenBindings(node)).length === 0) return node;\n\t\tconst next = applyBindings(node, state, modesOf(node), options);\n\t\tif (next !== node) changed = true;\n\t\treturn next as TNode;\n\t});\n\treturn changed ? out : (nodes as TNode[]);\n}\n\nexport interface CanvasTokenBindingInfo {\n\tproperty: CanvasTokenProperty;\n\tvariableId: string;\n\tvariable: TokenVariable | undefined;\n\tresolution: TokenResolution;\n}\n\n/** Each binding of a node with its variable and resolution (for inspectors and diagnostics). */\nexport function inspectNodeTokens(\n\tnode: CanvasNode,\n\tnodes: readonly CanvasNode[],\n\tstate: TokenState,\n\toptions: CanvasTokenOptions = {}\n): CanvasTokenBindingInfo[] {\n\tconst modes = getEffectiveTokenModes(node, nodes, state, options);\n\tconst out: CanvasTokenBindingInfo[] = [];\n\tfor (const [key, variableId] of Object.entries(getNodeTokenBindings(node))) {\n\t\tconst property = getCanvasTokenProperty(key, options);\n\t\tif (!property) continue;\n\t\tout.push({ property, variableId, variable: getTokenVariable(state, variableId), resolution: resolveToken(state, variableId, modes) });\n\t}\n\treturn out;\n}\n\nfunction setDataRecord(node: CanvasNode, key: string, record: Record<string, string>): CanvasNode {\n\tconst data = { ...node.data };\n\tif (Object.keys(record).length === 0) delete data[key];\n\telse data[key] = record;\n\treturn { ...node, data };\n}\n\n/**\n * Binds `property` of each node in `ids` to `variableId` (or unbinds with `null`, keeping the\n * current value) and resolves. Throws `type-mismatch` when the variable's type doesn't fit the\n * property, `not-found` for unknown variables or properties. Nodes the property doesn't apply\n * to are left alone.\n */\nexport function bindCanvasToken<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tids: Iterable<string>,\n\tpropertyKey: string,\n\tvariableId: string | null,\n\tstate: TokenState,\n\toptions: CanvasTokenOptions = {}\n): TNode[] {\n\tconst property = getCanvasTokenProperty(propertyKey, options);\n\tif (!property) throw new TokenError('not-found', `No bindable property \\`${propertyKey}\\``);\n\tif (variableId !== null) {\n\t\tconst variable = getTokenVariable(state, variableId);\n\t\tif (!variable) throw new TokenError('not-found', `No variable \\`${variableId}\\``);\n\t\tif (variable.type !== property.type) {\n\t\t\tthrow new TokenError('type-mismatch', `${property.label} takes a ${property.type} variable; \\`${variable.name}\\` is a ${variable.type}`);\n\t\t}\n\t}\n\tconst targets = new Set(ids);\n\tlet changed = false;\n\tconst bound = nodes.map((node) => {\n\t\tif (!targets.has(node.id) || !property.appliesTo(node)) return node;\n\t\tconst bindings = { ...getNodeTokenBindings(node) };\n\t\tif (variableId === null) {\n\t\t\tif (!(propertyKey in bindings)) return node;\n\t\t\tdelete bindings[propertyKey];\n\t\t} else {\n\t\t\tif (bindings[propertyKey] === variableId) return node;\n\t\t\tbindings[propertyKey] = variableId;\n\t\t}\n\t\tchanged = true;\n\t\treturn setDataRecord(node, TOKEN_BINDINGS_DATA_KEY, bindings) as TNode;\n\t});\n\treturn changed ? resolveCanvasTokens(bound, state, options) : (nodes as TNode[]);\n}\n\n/**\n * Sets (or with `null` clears, back to \"Auto\") the explicit mode of `collectionId` on each node\n * in `ids`, then resolves, so descendants follow.\n */\nexport function setCanvasTokenMode<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tids: Iterable<string>,\n\tcollectionId: string,\n\tmodeId: string | null,\n\tstate: TokenState,\n\toptions: CanvasTokenOptions = {}\n): TNode[] {\n\tif (modeId !== null) {\n\t\tconst collection = getTokenCollection(state, collectionId);\n\t\tif (!collection) throw new TokenError('not-found', `No collection \\`${collectionId}\\``);\n\t\tif (!collection.modes.some((m) => m.id === modeId)) throw new TokenError('not-found', `No mode \\`${modeId}\\` in \\`${collection.name}\\``);\n\t}\n\tconst targets = new Set(ids);\n\tlet changed = false;\n\tconst next = nodes.map((node) => {\n\t\tif (!targets.has(node.id)) return node;\n\t\tconst modes = { ...getNodeTokenModes(node) };\n\t\tif (modeId === null) {\n\t\t\tif (!(collectionId in modes)) return node;\n\t\t\tdelete modes[collectionId];\n\t\t} else {\n\t\t\tif (modes[collectionId] === modeId) return node;\n\t\t\tmodes[collectionId] = modeId;\n\t\t}\n\t\tchanged = true;\n\t\treturn setDataRecord(node, TOKEN_MODES_DATA_KEY, modes) as TNode;\n\t});\n\treturn changed ? resolveCanvasTokens(next, state, options) : (nodes as TNode[]);\n}\n\n/**\n * Detaches bindings to `variableIds` (default: every binding whose variable is missing from\n * `state`): each property keeps its value resolved in `state` and the binding is removed. Pass\n * the state from *before* a delete (`TokenChangeEvent.previous`) so deleted variables still\n * resolve.\n */\nexport function detachTokenBindings<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tstate: TokenState,\n\tvariableIds?: Iterable<string>,\n\toptions: CanvasTokenOptions = {}\n): TNode[] {\n\tconst only = variableIds ? new Set(variableIds) : null;\n\tconst isDetached = (id: string) => (only ? only.has(id) : !getTokenVariable(state, id));\n\tconst { modesOf } = modesResolver(nodes, state, options);\n\tlet changed = false;\n\tconst out = nodes.map((node) => {\n\t\tconst bindings = getNodeTokenBindings(node);\n\t\tconst gone = Object.entries(bindings).filter(([, id]) => isDetached(id));\n\t\tif (gone.length === 0) return node;\n\t\tlet next = applyBindings(node, state, modesOf(node), options, new Set(gone.map(([, id]) => id)));\n\t\tconst kept = { ...bindings };\n\t\tfor (const [key] of gone) delete kept[key];\n\t\tnext = setDataRecord(next, TOKEN_BINDINGS_DATA_KEY, kept);\n\t\tchanged = true;\n\t\treturn next as TNode;\n\t});\n\treturn changed ? out : (nodes as TNode[]);\n}\n\n/**\n * Removes explicit modes `state` no longer knows (deleted collections or modes), like figcraft\n * resetting such layers to \"Auto\". Resolution already ignores them; this tidies the data.\n */\nexport function pruneCanvasTokenModes<TNode extends CanvasNode>(nodes: readonly TNode[], state: TokenState): TNode[] {\n\tlet changed = false;\n\tconst out = nodes.map((node) => {\n\t\tconst modes = getNodeTokenModes(node);\n\t\tconst valid: Record<string, string> = {};\n\t\tvalidModes(state, modes, valid);\n\t\tif (Object.keys(valid).length === Object.keys(modes).length) return node;\n\t\tchanged = true;\n\t\treturn setDataRecord(node, TOKEN_MODES_DATA_KEY, valid) as TNode;\n\t});\n\treturn changed ? out : (nodes as TNode[]);\n}\n","// Portions ported from filmcraft-main/crates/project/src/keyframe.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport { getNodeSize, readStyleSize, withStyleSize } from '../arrange.js';\nimport { bezierPathLength, bezierPathPointAtLength } from '../pathops/bezier.js';\nimport { getCanvasTokenProperty, readNodeStyleProperty, writeNodeStyleProperty } from '../tokenBindings.js';\nimport { withStyleOpacity } from '../trajectory.js';\nimport type { CanvasNode } from '../types.js';\nimport { ANIMATION_TIME_EPSILON, isAnimationKeyframe, normalizeKeyframes, type AnimationKeyframe, type AnimationTrack, type AnimationValue, type AnimationValueType } from '../../animation/keyframes.js';\n\nexport { ANIMATION_TIME_EPSILON, isAnimationKeyframe, normalizeKeyframes, type AnimationKeyframe, type AnimationTrack, type AnimationValue, type AnimationValueType };\n\n/**\n * Keyframe animation for canvas nodes.\n *\n * A node's animation lives in `node.data.animation`, so it is copied, pasted, duplicated and\n * serialised with the node (component instances copy it too). Each animatable property has a\n * track of keyframes `{ time (ms), value, easing }`; the easing governs the segment *leaving*\n * the keyframe (filmcraft / After Effects convention).\n */\n\n/** Data key holding a node's animation. */\nexport const ANIMATION_DATA_KEY = 'animation';\n\n/** A path a node's centre follows, driven by the `motionPath` track (progress 0–1). */\nexport interface AnimationMotionPath {\n\t/** SVG path data in flow coordinates (the parent's when the node has a `parentId`). */\n\td: string;\n\t/** Rotate the node along the path's tangent (default `false`). */\n\tautoRotate?: boolean;\n}\n\nexport interface NodeAnimation {\n\t/** Property key → track. */\n\ttracks: Record<string, AnimationTrack>;\n\tmotionPath?: AnimationMotionPath;\n}\n\n/** A node property keyframes can drive. */\nexport interface AnimatableProperty {\n\tkey: string;\n\tlabel: string;\n\ttype: AnimationValueType;\n\t/** Values are clamped into `[min, max]` when written. */\n\tmin?: number;\n\tmax?: number;\n\t/** Unit for display (`px`, `°`, …). */\n\tunit?: string;\n\t/** The node's current (static) value, or `undefined` when it has none. */\n\tread(node: CanvasNode): AnimationValue | undefined;\n\t/** Returns the node with `value` written (a new object; the input is never mutated). */\n\twrite(node: CanvasNode, value: AnimationValue): CanvasNode;\n}\n\nconst num = (value: unknown): number | undefined => {\n\tif (typeof value === 'number') return Number.isFinite(value) ? value : undefined;\n\tif (typeof value === 'string') {\n\t\tconst n = parseFloat(value);\n\t\treturn Number.isFinite(n) ? n : undefined;\n\t}\n\treturn undefined;\n};\n\nconst tidy = (v: number) => Math.round(v * 10000) / 10000;\n\nfunction tokenBacked(key: string, label: string, type: AnimationValueType, extra: Partial<AnimatableProperty> = {}): AnimatableProperty {\n\treturn {\n\t\tkey,\n\t\tlabel,\n\t\ttype,\n\t\t...extra,\n\t\tread: (node) => {\n\t\t\tconst p = getCanvasTokenProperty(key);\n\t\t\tconst v = p?.appliesTo(node) ? p.read(node) : undefined;\n\t\t\treturn typeof v === 'number' || typeof v === 'string' ? v : undefined;\n\t\t},\n\t\twrite: (node, value) => {\n\t\t\tconst p = getCanvasTokenProperty(key);\n\t\t\treturn p?.appliesTo(node) ? p.write(node, value) : node;\n\t\t}\n\t};\n}\n\nfunction sizeProperty(key: 'width' | 'height', label: string): AnimatableProperty {\n\treturn {\n\t\tkey,\n\t\tlabel,\n\t\ttype: 'number',\n\t\tmin: 0,\n\t\tunit: 'px',\n\t\tread: (node) => num(node[key]) ?? readStyleSize(node.style)[key] ?? num(node.measured?.[key]),\n\t\twrite: (node, value) => {\n\t\t\tconst v = tidy(Math.max(0, value as number));\n\t\t\treturn { ...node, [key]: v, style: withStyleSize(node.style, { [key]: v }) };\n\t\t}\n\t};\n}\n\nfunction motionPathPoint(node: CanvasNode, progress: number) {\n\tconst path = getNodeAnimation(node)?.motionPath;\n\tif (!path || typeof path.d !== 'string') return null;\n\ttry {\n\t\tconst length = bezierPathLength(path.d);\n\t\treturn bezierPathPointAtLength(path.d, Math.min(1, Math.max(0, progress)) * length);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * The built-in animatable properties, in the order they are applied (`motionPath` comes after\n * `x` / `y` / `rotation` so it wins over them).\n *\n * Rotation and scale use the CSS individual transform properties (`style.rotate`,\n * `style.scale`), which compose with the `transform` xyflow positions nodes with.\n */\nexport const ANIMATABLE_PROPERTIES: readonly AnimatableProperty[] = [\n\t{\n\t\tkey: 'x',\n\t\tlabel: 'X',\n\t\ttype: 'number',\n\t\tunit: 'px',\n\t\tread: (node) => num(node.position?.x),\n\t\twrite: (node, value) => ({ ...node, position: { ...node.position, x: tidy(value as number) } })\n\t},\n\t{\n\t\tkey: 'y',\n\t\tlabel: 'Y',\n\t\ttype: 'number',\n\t\tunit: 'px',\n\t\tread: (node) => num(node.position?.y),\n\t\twrite: (node, value) => ({ ...node, position: { ...node.position, y: tidy(value as number) } })\n\t},\n\tsizeProperty('width', 'Width'),\n\tsizeProperty('height', 'Height'),\n\t{\n\t\tkey: 'rotation',\n\t\tlabel: 'Rotation',\n\t\ttype: 'number',\n\t\tunit: '°',\n\t\tread: (node) => num(readNodeStyleProperty(node.style, 'rotate')) ?? 0,\n\t\twrite: (node, value) => ({\n\t\t\t...node,\n\t\t\tstyle: writeNodeStyleProperty(node.style, 'rotate', `${tidy(value as number)}deg`)\n\t\t})\n\t},\n\t{\n\t\tkey: 'scale',\n\t\tlabel: 'Scale',\n\t\ttype: 'number',\n\t\tmin: 0,\n\t\tread: (node) => num(readNodeStyleProperty(node.style, 'scale')) ?? 1,\n\t\twrite: (node, value) => ({\n\t\t\t...node,\n\t\t\tstyle: writeNodeStyleProperty(node.style ?? {}, 'scale', tidy(Math.max(0, value as number)), true)\n\t\t})\n\t},\n\t{\n\t\tkey: 'opacity',\n\t\tlabel: 'Opacity',\n\t\ttype: 'number',\n\t\tmin: 0,\n\t\tmax: 1,\n\t\tread: (node) => num(readNodeStyleProperty(node.style, 'opacity')) ?? 1,\n\t\twrite: (node, value) => ({\n\t\t\t...node,\n\t\t\tstyle: withStyleOpacity(node.style, tidy(Math.min(1, Math.max(0, value as number))), typeof node.style === 'string' ? 'string' : 'object')\n\t\t})\n\t},\n\ttokenBacked('fill', 'Fill', 'color'),\n\ttokenBacked('stroke', 'Stroke', 'color'),\n\ttokenBacked('strokeWidth', 'Stroke width', 'number', { min: 0, unit: 'px' }),\n\ttokenBacked('cornerRadius', 'Corner radius', 'number', { min: 0, unit: 'px' }),\n\t{\n\t\tkey: 'motionPath',\n\t\tlabel: 'Motion path',\n\t\ttype: 'number',\n\t\tmin: 0,\n\t\tmax: 1,\n\t\tread: () => undefined,\n\t\twrite: (node, value) => {\n\t\t\tconst at = motionPathPoint(node, value as number);\n\t\t\tif (!at) return node;\n\t\t\tconst { width, height } = getNodeSize(node);\n\t\t\tlet next: CanvasNode = {\n\t\t\t\t...node,\n\t\t\t\tposition: { x: tidy(at.point[0] - width / 2), y: tidy(at.point[1] - height / 2) }\n\t\t\t};\n\t\t\tif (getNodeAnimation(node)?.motionPath?.autoRotate) {\n\t\t\t\tconst base = num(readNodeStyleProperty(node.style, 'rotate')) ?? 0;\n\t\t\t\tnext = { ...next, style: writeNodeStyleProperty(next.style, 'rotate', `${tidy(base + (at.angle * 180) / Math.PI)}deg`) };\n\t\t\t}\n\t\t\treturn next;\n\t\t}\n\t}\n];\n\nexport interface AnimationPropertyOptions {\n\t/** Animatable properties (default {@link ANIMATABLE_PROPERTIES}); add your own to extend. */\n\tproperties?: readonly AnimatableProperty[];\n}\n\nexport function getAnimatableProperty(key: string, options: AnimationPropertyOptions = {}): AnimatableProperty | undefined {\n\treturn (options.properties ?? ANIMATABLE_PROPERTIES).find((p) => p.key === key);\n}\n\n/** Clamps a value into a property's range (numbers) or returns it unchanged. */\nexport function clampAnimationValue(property: AnimatableProperty | undefined, value: AnimationValue): AnimationValue {\n\tif (!property || typeof value !== 'number') return value;\n\tlet v = value;\n\tif (property.min !== undefined) v = Math.max(property.min, v);\n\tif (property.max !== undefined) v = Math.min(property.max, v);\n\treturn v;\n}\n\n/** The node's animation (tracks with at least one valid keyframe), or `null`. */\nexport function getNodeAnimation(node: CanvasNode | undefined | null): NodeAnimation | null {\n\tconst raw = node?.data?.[ANIMATION_DATA_KEY];\n\tif (typeof raw !== 'object' || raw === null || typeof raw.tracks !== 'object' || raw.tracks === null) return null;\n\treturn raw as NodeAnimation;\n}\n\n/** The track for `property` on a node, if it has keyframes. */\nexport function getAnimationTrack(node: CanvasNode | undefined | null, property: string): AnimationTrack | undefined {\n\tconst track = getNodeAnimation(node)?.tracks[property];\n\treturn track && Array.isArray(track.keyframes) && normalizeKeyframes(track.keyframes).length > 0 ? track : undefined;\n}\n\n/** Whether the node has any (enabled or disabled) keyframes. */\nexport function isNodeAnimated(node: CanvasNode | undefined | null): boolean {\n\tconst animation = getNodeAnimation(node);\n\tif (!animation) return false;\n\treturn Object.keys(animation.tracks).some((key) => getAnimationTrack(node, key) !== undefined);\n}\n\n/** Property keys of the node's tracks (in registry order, unknown keys last). */\nexport function getAnimatedProperties(node: CanvasNode | undefined | null, options: AnimationPropertyOptions = {}): string[] {\n\tconst animation = getNodeAnimation(node);\n\tif (!animation) return [];\n\tconst keys = Object.keys(animation.tracks).filter((k) => getAnimationTrack(node, k));\n\tconst order = (options.properties ?? ANIMATABLE_PROPERTIES).map((p) => p.key);\n\tconst rank = (k: string) => (order.includes(k) ? order.indexOf(k) : order.length);\n\treturn keys.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));\n}\n\n/**\n * Returns the node with its animation replaced (`null` or no tracks removes the key). Empty\n * tracks are dropped.\n */\nexport function withNodeAnimation<TNode extends CanvasNode>(node: TNode, animation: NodeAnimation | null): TNode {\n\tconst tracks: Record<string, AnimationTrack> = {};\n\tif (animation) {\n\t\tfor (const [key, track] of Object.entries(animation.tracks)) {\n\t\t\tif (track && Array.isArray(track.keyframes) && track.keyframes.length > 0) tracks[key] = track;\n\t\t}\n\t}\n\tconst data: Record<string, any> = { ...(node.data ?? {}) };\n\tif (Object.keys(tracks).length === 0 && !animation?.motionPath) delete data[ANIMATION_DATA_KEY];\n\telse data[ANIMATION_DATA_KEY] = { ...animation, tracks };\n\treturn { ...node, data };\n}\n","// Portions ported from filmcraft-main/crates/project/src/keyframe.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport type { CanvasNode } from '../types.js';\nimport { evaluateTrack } from '../../animation/evaluate.js';\nimport {\n\tANIMATABLE_PROPERTIES,\n\tclampAnimationValue,\n\tgetNodeAnimation,\n\tnormalizeKeyframes,\n\ttype AnimatableProperty,\n\ttype AnimationPropertyOptions,\n\ttype AnimationValue\n} from './model.js';\n\nexport { evaluateTrack, interpolateAnimationValue, wrapAnimationTime, type AnimationLoopMode } from '../../animation/evaluate.js';\n\n/** Nodes, or a document holding them. */\nexport type AnimationDocument = readonly CanvasNode[] | { readonly nodes: readonly CanvasNode[] };\n\n/** Evaluated values: node id → property key → value. */\nexport type AnimationOverrides = Record<string, Record<string, AnimationValue>>;\n\nconst nodesOf = (doc: AnimationDocument): readonly CanvasNode[] => (Array.isArray(doc) ? doc : (doc as { nodes: readonly CanvasNode[] }).nodes);\n\n/** The last keyframe time over the enabled tracks of the nodes (0 when nothing is animated). */\nexport function getAnimationDuration(doc: AnimationDocument): number {\n\tlet end = 0;\n\tfor (const node of nodesOf(doc)) {\n\t\tconst animation = getNodeAnimation(node);\n\t\tif (!animation) continue;\n\t\tfor (const track of Object.values(animation.tracks)) {\n\t\t\tif (!track || track.enabled === false) continue;\n\t\t\tconst keys = normalizeKeyframes(track.keyframes);\n\t\t\tif (keys.length > 0) end = Math.max(end, keys[keys.length - 1].time);\n\t\t}\n\t}\n\treturn end;\n}\n\nexport interface EvaluateAnimationOptions extends AnimationPropertyOptions {\n\t/** Only evaluate these nodes. */\n\tids?: Iterable<string>;\n}\n\n/**\n * Evaluates every enabled track at `time`: node id → property → value. The nodes are not\n * touched; apply the result with `applyAnimationOverrides` (or use `applyAnimation`).\n */\nexport function evaluateAnimation(doc: AnimationDocument, time: number, options: EvaluateAnimationOptions = {}): AnimationOverrides {\n\tconst only = options.ids ? new Set(options.ids) : null;\n\tconst out: AnimationOverrides = {};\n\tfor (const node of nodesOf(doc)) {\n\t\tif (only && !only.has(node.id)) continue;\n\t\tconst animation = getNodeAnimation(node);\n\t\tif (!animation) continue;\n\t\tlet values: Record<string, AnimationValue> | undefined;\n\t\tfor (const [key, track] of Object.entries(animation.tracks)) {\n\t\t\tif (!track || track.enabled === false) continue;\n\t\t\tconst property = (options.properties ?? ANIMATABLE_PROPERTIES).find((p) => p.key === key);\n\t\t\tconst value = evaluateTrack(track, time, property?.type ?? 'number');\n\t\t\tif (value === undefined) continue;\n\t\t\t(values ??= {})[key] = clampAnimationValue(property, value);\n\t\t}\n\t\tif (values) out[node.id] = values;\n\t}\n\treturn out;\n}\n\n/**\n * Writes evaluated values into copies of the nodes (in the registry's order). Nodes without\n * overrides keep their identity, and the input array is returned when nothing changes.\n */\nexport function applyAnimationOverrides<TNode extends CanvasNode>(nodes: readonly TNode[], overrides: AnimationOverrides, options: AnimationPropertyOptions = {}): TNode[] {\n\tconst properties: readonly AnimatableProperty[] = options.properties ?? ANIMATABLE_PROPERTIES;\n\tlet changed = false;\n\tconst out = nodes.map((node) => {\n\t\tconst values = overrides[node.id];\n\t\tif (!values) return node;\n\t\tlet next: CanvasNode = node;\n\t\tfor (const property of properties) {\n\t\t\tif (!(property.key in values)) continue;\n\t\t\tnext = property.write(next, values[property.key]);\n\t\t}\n\t\tif (next !== node) changed = true;\n\t\treturn next as TNode;\n\t});\n\treturn changed ? out : (nodes as TNode[]);\n}\n\n/**\n * The nodes as they look at `time`: `applyAnimationOverrides(nodes, evaluateAnimation(nodes, time))`.\n * Run it after `resolveCanvasTokens` so animated values win over token values while playing.\n */\nexport function applyAnimation<TNode extends CanvasNode>(nodes: readonly TNode[], time: number, options: EvaluateAnimationOptions = {}): TNode[] {\n\treturn applyAnimationOverrides(nodes, evaluateAnimation(nodes, time, options), options);\n}\n","// Portions ported from filmcraft-main/crates/engine/src/keyframe_move.rs and crates/project/src/keyframe.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport { getNodeSize } from '../arrange.js';\nimport type { CanvasNode } from '../types.js';\nimport { normalizeTokenColor } from '../../tokens/color.js';\nimport { isAnimationEasing, parseCssEasing, type AnimationEasing } from './easing.js';\nimport { evaluateTrack } from './evaluate.js';\nimport {\n\tANIMATABLE_PROPERTIES,\n\tANIMATION_TIME_EPSILON,\n\tclampAnimationValue,\n\tgetAnimatableProperty,\n\tgetAnimationTrack,\n\tgetNodeAnimation,\n\tnormalizeKeyframes,\n\twithNodeAnimation,\n\ttype AnimatableProperty,\n\ttype AnimationKeyframe,\n\ttype AnimationPropertyOptions,\n\ttype AnimationTrack,\n\ttype AnimationValue,\n\ttype NodeAnimation\n} from './model.js';\n\n/** Identifies one keyframe: node, property and time. */\nexport interface KeyframeRef {\n\tnodeId: string;\n\tproperty: string;\n\ttime: number;\n}\n\nexport type AnimationEditErrorCode =\n\t| 'unknown_node'\n\t| 'unknown_property'\n\t| 'unknown_keyframe'\n\t| 'duplicate_keyframe'\n\t| 'invalid_time'\n\t| 'invalid_value'\n\t| 'invalid_easing'\n\t| 'invalid_factor'\n\t| 'too_many_keyframes'\n\t| 'no_movement_range';\n\n/** Thrown by the editing functions for invalid requests; nothing is changed when it is thrown. */\nexport class AnimationEditError extends Error {\n\treadonly code: AnimationEditErrorCode;\n\tconstructor(code: AnimationEditErrorCode, message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'AnimationEditError';\n\t\tthis.code = code;\n\t}\n}\n\n/** Most keyframes one move / scale / delete may name (as filmcraft's `effects.moveKeyframes`). */\nexport const MAX_KEYFRAME_REFS = 4096;\n\nconst sameTime = (a: number, b: number) => Math.abs(a - b) <= ANIMATION_TIME_EPSILON;\nconst refKey = (r: { nodeId: string; property: string }) => `${r.nodeId}\\u0000${r.property}`;\n\n/** Snaps a time to the nearest frame at `fps`. */\nexport function snapTimeToFrame(time: number, fps: number): number {\n\tif (!(fps > 0)) return time;\n\tconst frame = 1000 / fps;\n\t// Rounded to a microsecond so frame times compare equal (500, not 500.00000000000006).\n\treturn Math.round(Math.round(time / frame) * frame * 1000) / 1000;\n}\n\nfunction coerceValue(property: AnimatableProperty | undefined, value: unknown): AnimationValue {\n\tconst type = property?.type ?? (typeof value === 'string' && normalizeTokenColor(value) !== null ? 'color' : 'number');\n\tif (type === 'color') {\n\t\tconst color = normalizeTokenColor(value);\n\t\tif (color === null) throw new AnimationEditError('invalid_value', `\"${String(value)}\" is not a colour.`);\n\t\treturn color;\n\t}\n\tconst n = typeof value === 'string' && value.trim() !== '' ? Number(value) : value;\n\tif (typeof n !== 'number' || !Number.isFinite(n)) throw new AnimationEditError('invalid_value', `\"${String(value)}\" is not a number.`);\n\treturn clampAnimationValue(property, n);\n}\n\nfunction coerceEasing(easing: unknown): AnimationEasing {\n\tif (typeof easing === 'string') {\n\t\tconst parsed = parseCssEasing(easing);\n\t\tif (parsed) return parsed;\n\t} else if (isAnimationEasing(easing)) return easing;\n\tthrow new AnimationEditError('invalid_easing', `Unknown easing ${JSON.stringify(easing)}.`);\n}\n\nfunction coerceTime(time: unknown): number {\n\tif (typeof time !== 'number' || !Number.isFinite(time)) throw new AnimationEditError('invalid_time', `Invalid time ${String(time)}.`);\n\treturn Math.max(0, time);\n}\n\n/**\n * Copy-on-write editing session over a node array: tracks are normalised (sorted, duplicates\n * merged) when first touched and written back by `commit`.\n */\nclass Draft<TNode extends CanvasNode> {\n\tprivate readonly byId: Map<string, TNode>;\n\tprivate readonly animations = new Map<string, NodeAnimation>();\n\tprivate readonly nodeWrites = new Map<string, TNode>();\n\n\tconstructor(private readonly nodes: readonly TNode[], readonly options: AnimationPropertyOptions = {}) {\n\t\tthis.byId = new Map(nodes.map((n) => [n.id, n]));\n\t}\n\n\tnode(id: string): TNode {\n\t\tconst node = this.nodeWrites.get(id) ?? this.byId.get(id);\n\t\tif (!node) throw new AnimationEditError('unknown_node', `Node \"${id}\" does not exist.`);\n\t\treturn node;\n\t}\n\n\thas(id: string): boolean {\n\t\treturn this.byId.has(id);\n\t}\n\n\tanimation(id: string): NodeAnimation {\n\t\tlet animation = this.animations.get(id);\n\t\tif (!animation) {\n\t\t\tconst current = getNodeAnimation(this.node(id));\n\t\t\tconst tracks: Record<string, AnimationTrack> = {};\n\t\t\tfor (const [key, track] of Object.entries(current?.tracks ?? {})) {\n\t\t\t\tif (track) tracks[key] = { ...track, keyframes: normalizeKeyframes(track.keyframes) };\n\t\t\t}\n\t\t\tanimation = { ...(current ?? {}), tracks };\n\t\t\tthis.animations.set(id, animation);\n\t\t}\n\t\treturn animation;\n\t}\n\n\tkeys(id: string, property: string): AnimationKeyframe[] {\n\t\treturn this.animation(id).tracks[property]?.keyframes ?? [];\n\t}\n\n\ttrack(id: string, property: string): AnimationTrack | undefined {\n\t\treturn this.animation(id).tracks[property];\n\t}\n\n\tsetKeys(id: string, property: string, keyframes: AnimationKeyframe[]) {\n\t\tconst animation = this.animation(id);\n\t\tconst sorted = [...keyframes].sort((a, b) => a.time - b.time);\n\t\tif (sorted.length === 0) delete animation.tracks[property];\n\t\telse animation.tracks[property] = { ...(animation.tracks[property] ?? {}), keyframes: sorted };\n\t}\n\n\tsetMotionPath(id: string, motionPath: NodeAnimation['motionPath'] | undefined) {\n\t\tconst animation = this.animation(id);\n\t\tif (motionPath) animation.motionPath = { ...motionPath };\n\t\telse delete animation.motionPath;\n\t}\n\n\twriteNode(id: string, fn: (node: TNode) => TNode) {\n\t\tthis.nodeWrites.set(id, fn(this.node(id)));\n\t}\n\n\tfind(ref: KeyframeRef): number {\n\t\tconst keys = this.keys(ref.nodeId, ref.property);\n\t\treturn keys.findIndex((k) => sameTime(k.time, ref.time));\n\t}\n\n\tproperty(key: string): AnimatableProperty | undefined {\n\t\treturn getAnimatableProperty(key, this.options);\n\t}\n\n\tcommit(): TNode[] {\n\t\tif (this.animations.size === 0 && this.nodeWrites.size === 0) return this.nodes as TNode[];\n\t\treturn this.nodes.map((node) => {\n\t\t\tconst written = this.nodeWrites.get(node.id);\n\t\t\tconst animation = this.animations.get(node.id);\n\t\t\tif (!written && !animation) return node;\n\t\t\tconst base = written ?? node;\n\t\t\treturn animation ? withNodeAnimation(base, animation) : base;\n\t\t});\n\t}\n}\n\nfunction validateRefs(draft: Draft<CanvasNode>, refs: readonly KeyframeRef[], allowEmpty = false) {\n\tif (!Array.isArray(refs) || (!allowEmpty && refs.length === 0)) throw new AnimationEditError('unknown_keyframe', 'Name at least one keyframe.');\n\tif (refs.length > MAX_KEYFRAME_REFS) throw new AnimationEditError('too_many_keyframes', `At most ${MAX_KEYFRAME_REFS} keyframes per edit.`);\n\tconst seen = new Map<string, number[]>();\n\tfor (const ref of refs) {\n\t\tif (!ref || typeof ref.nodeId !== 'string' || typeof ref.property !== 'string' || typeof ref.time !== 'number' || !Number.isFinite(ref.time)) {\n\t\t\tthrow new AnimationEditError('unknown_keyframe', 'Invalid keyframe reference.');\n\t\t}\n\t\tdraft.node(ref.nodeId);\n\t\tif (draft.find(ref) === -1) throw new AnimationEditError('unknown_keyframe', `No ${ref.property} keyframe at ${ref.time}ms on \"${ref.nodeId}\".`);\n\t\tconst times = seen.get(refKey(ref)) ?? [];\n\t\tif (times.some((t) => sameTime(t, ref.time))) throw new AnimationEditError('duplicate_keyframe', 'A keyframe is named twice.');\n\t\ttimes.push(ref.time);\n\t\tseen.set(refKey(ref), times);\n\t}\n}\n\n/** Static value of a property, for keying (sizes fall back to the measured size). */\nexport function readAnimatableValue(node: CanvasNode, property: AnimatableProperty): AnimationValue | undefined {\n\tconst value = property.read(node);\n\tif (value !== undefined) return value;\n\tif (property.key === 'width' || property.key === 'height') return getNodeSize(node)[property.key];\n\treturn undefined;\n}\n\n/** The value a property shows at `time`: its track's value when animated, else its static value. */\nexport function getAnimatedValue(node: CanvasNode, propertyKey: string, time: number, options: AnimationPropertyOptions = {}): AnimationValue | undefined {\n\tconst property = getAnimatableProperty(propertyKey, options);\n\tconst track = getAnimationTrack(node, propertyKey);\n\tif (track && track.enabled !== false) return evaluateTrack(track, time, property?.type ?? 'number');\n\treturn property ? readAnimatableValue(node, property) : undefined;\n}\n\nexport interface SetKeyframeOptions extends AnimationPropertyOptions {\n\t/** Easing of the segment leaving the keyframe (default: kept, or the previous keyframe's). */\n\teasing?: AnimationEasing | string;\n}\n\nfunction putKey(draft: Draft<any>, nodeId: string, property: string, time: number, value: AnimationValue, easing?: AnimationEasing, inherit = true) {\n\tconst keys = [...draft.keys(nodeId, property)];\n\tconst at = keys.findIndex((k) => sameTime(k.time, time));\n\tif (at >= 0) {\n\t\tkeys[at] = { ...keys[at], value, ...(easing !== undefined ? { easing } : {}) };\n\t} else {\n\t\t// New keys take the easing of the keyframe before them (filmcraft `Param::set_at`).\n\t\tconst before = [...keys].reverse().find((k) => k.time < time);\n\t\tconst inherited = easing ?? (inherit ? before?.easing : undefined);\n\t\tkeys.push({ time, value, ...(inherited !== undefined ? { easing: inherited } : {}) });\n\t}\n\tdraft.setKeys(nodeId, property, keys);\n}\n\n/** Adds a keyframe, or replaces the value (and easing, when given) of the one at `time`. */\nexport function setKeyframe<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tnodeId: string,\n\tproperty: string,\n\ttime: number,\n\tvalue: AnimationValue,\n\toptions: SetKeyframeOptions = {}\n): TNode[] {\n\tconst draft = new Draft(nodes, options);\n\tdraft.node(nodeId);\n\tconst p = draft.property(property);\n\tif (!p) throw new AnimationEditError('unknown_property', `\"${property}\" cannot be animated.`);\n\tputKey(draft, nodeId, property, coerceTime(time), coerceValue(p, value), options.easing === undefined ? undefined : coerceEasing(options.easing));\n\treturn draft.commit();\n}\n\n/**\n * Keys the given properties of the nodes at `time` with the values they show there (the\n * \"add keyframe at playhead\" action). Without `properties`, keys every animated property, or\n * `x` and `y` for nodes that have none. Returns the new nodes and the keyed refs.\n */\nexport function addKeyframesAtTime<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tids: Iterable<string>,\n\ttime: number,\n\toptions: AnimationPropertyOptions & { properties?: readonly AnimatableProperty[]; keys?: readonly string[] } = {}\n): { nodes: TNode[]; refs: KeyframeRef[] } {\n\tconst draft = new Draft(nodes, options);\n\tconst t = coerceTime(time);\n\tconst refs: KeyframeRef[] = [];\n\tfor (const id of ids) {\n\t\tconst node = draft.node(id);\n\t\tconst animated = Object.keys(draft.animation(id).tracks);\n\t\tconst keys = options.keys ?? (animated.length > 0 ? animated : ['x', 'y']);\n\t\tfor (const key of keys) {\n\t\t\tconst p = draft.property(key);\n\t\t\tif (!p) throw new AnimationEditError('unknown_property', `\"${key}\" cannot be animated.`);\n\t\t\tconst value = getAnimatedValue(node, key, t, options);\n\t\t\tif (value === undefined) continue;\n\t\t\tputKey(draft, id, key, t, value);\n\t\t\trefs.push({ nodeId: id, property: key, time: t });\n\t\t}\n\t}\n\treturn { nodes: draft.commit(), refs };\n}\n\nexport interface KeyframePatch {\n\ttime?: number;\n\tvalue?: AnimationValue;\n\teasing?: AnimationEasing | string | null;\n}\n\n/** Changes one keyframe's time, value or easing (`easing: null` resets it to linear). */\nexport function updateKeyframe<TNode extends CanvasNode>(nodes: readonly TNode[], ref: KeyframeRef, patch: KeyframePatch, options: AnimationPropertyOptions = {}): TNode[] {\n\tconst draft = new Draft(nodes, options);\n\tvalidateRefs(draft, [ref]);\n\tconst keys = [...draft.keys(ref.nodeId, ref.property)];\n\tconst i = keys.findIndex((k) => sameTime(k.time, ref.time));\n\tconst next: AnimationKeyframe = { ...keys[i] };\n\tif (patch.value !== undefined) next.value = coerceValue(draft.property(ref.property), patch.value);\n\tif (patch.easing === null) delete next.easing;\n\telse if (patch.easing !== undefined) next.easing = coerceEasing(patch.easing);\n\tif (patch.time !== undefined) next.time = coerceTime(patch.time);\n\tkeys.splice(i, 1);\n\t// Moving onto another keyframe replaces it.\n\tconst rest = keys.filter((k) => !sameTime(k.time, next.time));\n\tdraft.setKeys(ref.nodeId, ref.property, [...rest, next]);\n\treturn draft.commit();\n}\n\n/** Sets the easing of the segments leaving the keyframes. */\nexport function setKeyframeEasing<TNode extends CanvasNode>(nodes: readonly TNode[], refs: readonly KeyframeRef[], easing: AnimationEasing | string | null, options: AnimationPropertyOptions = {}): TNode[] {\n\tconst draft = new Draft(nodes, options);\n\tvalidateRefs(draft, refs);\n\tconst value = easing === null ? null : coerceEasing(easing);\n\tfor (const ref of refs) {\n\t\tconst keys = draft.keys(ref.nodeId, ref.property).map((k) => {\n\t\t\tif (!sameTime(k.time, ref.time)) return k;\n\t\t\tconst { easing: _old, ...rest } = k;\n\t\t\treturn value === null ? rest : { ...rest, easing: value };\n\t\t});\n\t\tdraft.setKeys(ref.nodeId, ref.property, keys);\n\t}\n\treturn draft.commit();\n}\n\n/**\n * Deletes keyframes. When a track loses its last keyframe the node keeps that keyframe's\n * value as its static value, so it does not jump (filmcraft `remove_keyframe_at`).\n */\nexport function deleteKeyframes<TNode extends CanvasNode>(nodes: readonly TNode[], refs: readonly KeyframeRef[], options: AnimationPropertyOptions = {}): TNode[] {\n\tconst draft = new Draft(nodes, options);\n\tvalidateRefs(draft, refs);\n\tconst lanes = new Map<string, KeyframeRef[]>();\n\tfor (const ref of refs) lanes.set(refKey(ref), [...(lanes.get(refKey(ref)) ?? []), ref]);\n\tfor (const lane of lanes.values()) {\n\t\tconst { nodeId, property } = lane[0];\n\t\tconst keys = draft.keys(nodeId, property);\n\t\tconst left = keys.filter((k) => !lane.some((r) => sameTime(r.time, k.time)));\n\t\tif (left.length === 0) {\n\t\t\tconst p = draft.property(property);\n\t\t\tconst last = keys.find((k) => sameTime(k.time, lane[lane.length - 1].time)) ?? keys[keys.length - 1];\n\t\t\tif (p && property !== 'motionPath') draft.writeNode(nodeId, (n) => p.write(n, last.value) as typeof n);\n\t\t}\n\t\tdraft.setKeys(nodeId, property, left);\n\t}\n\treturn draft.commit();\n}\n\n/** Turns a track on or off (disabled tracks keep their keyframes but do not animate). */\nexport function setTrackEnabled<TNode extends CanvasNode>(nodes: readonly TNode[], nodeId: string, property: string, enabled: boolean): TNode[] {\n\tconst draft = new Draft(nodes);\n\tif (!draft.track(nodeId, property)) throw new AnimationEditError('unknown_property', `\"${nodeId}\" has no ${property} track.`);\n\tconst { enabled: _was, ...track } = draft.track(nodeId, property)!;\n\tdraft.animation(nodeId).tracks[property] = enabled ? track : { ...track, enabled: false };\n\treturn draft.commit();\n}\n\n/**\n * Removes a track. With `keepValueAt`, the node keeps the value the track had at that time\n * (filmcraft's stopwatch toggle).\n */\nexport function removeTrack<TNode extends CanvasNode>(nodes: readonly TNode[], nodeId: string, property: string, options: AnimationPropertyOptions & { keepValueAt?: number } = {}): TNode[] {\n\tconst draft = new Draft(nodes, options);\n\tconst track = draft.track(nodeId, property);\n\tif (!track) return nodes as TNode[];\n\tconst p = draft.property(property);\n\tif (options.keepValueAt !== undefined && p && property !== 'motionPath') {\n\t\tconst value = evaluateTrack(track, options.keepValueAt, p.type);\n\t\tif (value !== undefined) draft.writeNode(nodeId, (n) => p.write(n, value) as typeof n);\n\t}\n\tdraft.setKeys(nodeId, property, []);\n\tif (property === 'motionPath') draft.setMotionPath(nodeId, undefined);\n\treturn draft.commit();\n}\n\n/** Sets (or with `null` removes) the path the `motionPath` track moves the node along. */\nexport function setMotionPath<TNode extends CanvasNode>(nodes: readonly TNode[], nodeId: string, motionPath: { d: string; autoRotate?: boolean } | null): TNode[] {\n\tconst draft = new Draft(nodes);\n\tdraft.node(nodeId);\n\tif (motionPath && (typeof motionPath.d !== 'string' || motionPath.d.trim() === '')) {\n\t\tthrow new AnimationEditError('invalid_value', 'A motion path needs SVG path data.');\n\t}\n\tdraft.setMotionPath(nodeId, motionPath ?? undefined);\n\treturn draft.commit();\n}\n\nexport interface KeyframeRangeOptions {\n\t/** Earliest allowed time (default 0). */\n\tmin?: number;\n\t/** Latest allowed time (default unbounded). */\n\tmax?: number;\n}\n\nexport interface MoveKeyframesResult<TNode extends CanvasNode = CanvasNode> {\n\tnodes: TNode[];\n\t/** The delta applied (the request clamped so the whole group stays in range). */\n\tdelta: number;\n\t/** The moved keyframes at their new times, in request order. */\n\trefs: KeyframeRef[];\n}\n\n/**\n * Moves keyframes (across nodes and properties) by one common `delta`, so their relative timing\n * is kept. The delta is clamped so every key stays inside `[min, max]`; a clamped-to-zero move\n * returns the input nodes. Moved keys replace unselected keys at their destination, but never\n * each other: all originals are removed before any is re-inserted. Invalid or duplicate\n * references throw without changing anything.\n */\nexport function moveKeyframes<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\trefs: readonly KeyframeRef[],\n\tdelta: number,\n\toptions: KeyframeRangeOptions = {}\n): MoveKeyframesResult<TNode> {\n\tif (typeof delta !== 'number' || Number.isNaN(delta)) throw new AnimationEditError('invalid_time', 'The delta must be a number.');\n\tconst draft = new Draft(nodes);\n\tvalidateRefs(draft, refs);\n\tconst min = options.min ?? 0;\n\tconst max = options.max ?? Infinity;\n\tlet low = -Infinity;\n\tlet high = Infinity;\n\tfor (const ref of refs) {\n\t\tconst time = draft.keys(ref.nodeId, ref.property)[draft.find(ref)].time;\n\t\tlow = Math.max(low, min - time);\n\t\thigh = Math.min(high, max - time);\n\t}\n\tif (low > high + ANIMATION_TIME_EPSILON) throw new AnimationEditError('no_movement_range', 'The keyframes have no common movement range.');\n\tconst applied = Math.min(high, Math.max(low, delta));\n\tconst moved = refs.map((ref) => ({ ...ref, time: draft.keys(ref.nodeId, ref.property)[draft.find(ref)].time + applied }));\n\tif (Math.abs(applied) <= ANIMATION_TIME_EPSILON) return { nodes: nodes as TNode[], delta: 0, refs: refs.map((r) => ({ ...r })) };\n\tretime(draft, refs, (time) => time + applied);\n\treturn { nodes: draft.commit(), delta: applied, refs: moved };\n}\n\n/** Re-times the referenced keys of each lane: originals out first, then destinations replace. */\nfunction retime(draft: Draft<any>, refs: readonly KeyframeRef[], map: (time: number) => number) {\n\tconst lanes = new Map<string, KeyframeRef[]>();\n\tfor (const ref of refs) lanes.set(refKey(ref), [...(lanes.get(refKey(ref)) ?? []), ref]);\n\tfor (const lane of lanes.values()) {\n\t\tconst { nodeId, property } = lane[0];\n\t\tconst keys = draft.keys(nodeId, property);\n\t\tconst picked = keys.filter((k) => lane.some((r) => sameTime(r.time, k.time)));\n\t\tconst movedKeys = picked.map((k) => ({ ...k, time: map(k.time) }));\n\t\tconst rest = keys.filter((k) => !picked.includes(k) && !movedKeys.some((m) => sameTime(m.time, k.time)));\n\t\tdraft.setKeys(nodeId, property, [...rest, ...movedKeys]);\n\t}\n}\n\nexport interface ScaleKeyframesOptions extends KeyframeRangeOptions {\n\t/** Time that stays fixed (default: the earliest selected keyframe). */\n\tpivot?: number;\n}\n\n/**\n * Scales the spacing of keyframes in time around `pivot` (factor 2 = twice as slow). The factor\n * is reduced so every key stays inside `[min, max]`. Returns the factor applied.\n */\nexport function scaleKeyframes<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\trefs: readonly KeyframeRef[],\n\tfactor: number,\n\toptions: ScaleKeyframesOptions = {}\n): { nodes: TNode[]; factor: number; refs: KeyframeRef[] } {\n\tif (typeof factor !== 'number' || !Number.isFinite(factor) || factor <= 0) throw new AnimationEditError('invalid_factor', 'The scale factor must be a positive number.');\n\tconst draft = new Draft(nodes);\n\tvalidateRefs(draft, refs);\n\tconst times = refs.map((ref) => draft.keys(ref.nodeId, ref.property)[draft.find(ref)].time);\n\tconst pivot = options.pivot ?? Math.min(...times);\n\tconst min = options.min ?? 0;\n\tconst max = options.max ?? Infinity;\n\tlet applied = factor;\n\tfor (const t of times) {\n\t\tif (t > pivot && Number.isFinite(max)) applied = Math.min(applied, (max - pivot) / (t - pivot));\n\t\tif (t < pivot) applied = Math.min(applied, (pivot - min) / (pivot - t));\n\t}\n\tapplied = Math.max(applied, Number.EPSILON);\n\tconst map = (t: number) => pivot + (t - pivot) * applied;\n\tif (Math.abs(applied - 1) <= 1e-12) return { nodes: nodes as TNode[], factor: 1, refs: refs.map((r) => ({ ...r })) };\n\tretime(draft, refs, map);\n\treturn { nodes: draft.commit(), factor: applied, refs: refs.map((r, i) => ({ ...r, time: map(times[i]) })) };\n}\n\n/** Keyframe times on the nodes (all when `ids` is omitted), sorted and unique, minus `exclude`. */\nexport function getKeyframeTimes(nodes: readonly CanvasNode[], options: { ids?: Iterable<string>; exclude?: readonly KeyframeRef[] } = {}): number[] {\n\tconst only = options.ids ? new Set(options.ids) : null;\n\tconst excluded = new Map<string, number[]>();\n\tfor (const ref of options.exclude ?? []) excluded.set(refKey(ref), [...(excluded.get(refKey(ref)) ?? []), ref.time]);\n\tconst times: number[] = [];\n\tfor (const node of nodes) {\n\t\tif (only && !only.has(node.id)) continue;\n\t\tconst animation = getNodeAnimation(node);\n\t\tif (!animation) continue;\n\t\tfor (const [property, track] of Object.entries(animation.tracks)) {\n\t\t\tconst skip = excluded.get(refKey({ nodeId: node.id, property })) ?? [];\n\t\t\tfor (const k of normalizeKeyframes(track?.keyframes)) if (!skip.some((t) => sameTime(t, k.time))) times.push(k.time);\n\t\t}\n\t}\n\ttimes.sort((a, b) => a - b);\n\treturn times.filter((t, i) => i === 0 || !sameTime(t, times[i - 1]));\n}\n\nexport interface KeyframeSnap {\n\t/** The delta after snapping. */\n\tdelta: number;\n\t/** The time snapped to, or `null` when nothing was within the threshold. */\n\ttarget: number | null;\n}\n\n/**\n * Snaps a group move: the selected key (at `times + delta`) nearest to any target pulls the\n * whole group onto it when within `threshold` ms (convert the timeline's screen threshold,\n * e.g. 8px, with its scale). The group's spacing is kept.\n */\nexport function snapKeyframeDelta(times: readonly number[], delta: number, targets: readonly number[], threshold: number): KeyframeSnap {\n\tconst sorted = [...targets].filter(Number.isFinite).sort((a, b) => a - b);\n\tlet best: { distance: number; target: number; delta: number } | null = null;\n\tfor (const time of times) {\n\t\tconst at = time + delta;\n\t\tlet lo = 0;\n\t\tlet hi = sorted.length;\n\t\twhile (lo < hi) {\n\t\t\tconst mid = (lo + hi) >> 1;\n\t\t\tif (sorted[mid] < at) lo = mid + 1;\n\t\t\telse hi = mid;\n\t\t}\n\t\tfor (const i of [lo - 1, lo]) {\n\t\t\tconst target = sorted[i];\n\t\t\tif (target === undefined) continue;\n\t\t\tconst distance = Math.abs(target - at);\n\t\t\tif (distance <= threshold && (!best || distance < best.distance)) best = { distance, target, delta: delta + (target - at) };\n\t\t}\n\t}\n\treturn best ? { delta: best.delta, target: best.target } : { delta, target: null };\n}\n\n// -------------------------------------------------------------------------------------------\n// Copy / paste\n\n/** Clipboard payload for keyframes; times are offsets from the earliest copied keyframe. */\nexport interface AnimationKeyframeClipboard {\n\ttype: 'pixerate/keyframes';\n\tversion: 1;\n\tkeyframes: { nodeId: string; property: string; offset: number; value: AnimationValue; easing?: AnimationEasing }[];\n}\n\n/** Copies keyframes into a clipboard payload. */\nexport function copyKeyframes(nodes: readonly CanvasNode[], refs: readonly KeyframeRef[]): AnimationKeyframeClipboard {\n\tconst draft = new Draft(nodes);\n\tvalidateRefs(draft, refs);\n\tconst found = refs.map((ref) => ({ ref, key: draft.keys(ref.nodeId, ref.property)[draft.find(ref)] }));\n\tconst start = Math.min(...found.map((f) => f.key.time));\n\treturn {\n\t\ttype: 'pixerate/keyframes',\n\t\tversion: 1,\n\t\tkeyframes: found.map(({ ref, key }) => ({\n\t\t\tnodeId: ref.nodeId,\n\t\t\tproperty: ref.property,\n\t\t\toffset: key.time - start,\n\t\t\tvalue: key.value,\n\t\t\t...(key.easing !== undefined ? { easing: key.easing } : {})\n\t\t}))\n\t};\n}\n\n/** Reads a keyframe clipboard payload (object or JSON text), or `null`. */\nexport function parseKeyframeClipboard(input: unknown): AnimationKeyframeClipboard | null {\n\tlet value = input;\n\tif (typeof input === 'string') {\n\t\tif (input.length > 5_000_000) return null;\n\t\ttry {\n\t\t\tvalue = JSON.parse(input);\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\tif (typeof value !== 'object' || value === null) return null;\n\tconst clip = value as Record<string, any>;\n\tif (clip.type !== 'pixerate/keyframes' || clip.version !== 1 || !Array.isArray(clip.keyframes)) return null;\n\tconst keyframes = clip.keyframes.filter(\n\t\t(k: any) =>\n\t\t\tk &&\n\t\t\ttypeof k.nodeId === 'string' &&\n\t\t\ttypeof k.property === 'string' &&\n\t\t\ttypeof k.offset === 'number' &&\n\t\t\tNumber.isFinite(k.offset) &&\n\t\t\t((typeof k.value === 'number' && Number.isFinite(k.value)) || typeof k.value === 'string') &&\n\t\t\t(k.easing === undefined || isAnimationEasing(k.easing))\n\t);\n\treturn keyframes.length > 0 ? { type: 'pixerate/keyframes', version: 1, keyframes } : null;\n}\n\n/**\n * Pastes keyframes with their first key at `time`, replacing keys at the same times. Targets:\n * `nodeIds` (every target gets a single-node clip; several source nodes map onto the targets\n * in order), or the original nodes when omitted. Keys for missing nodes or unknown properties\n * are skipped. Returns the pasted refs.\n */\nexport function pasteKeyframes<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tclipboard: AnimationKeyframeClipboard,\n\ttime: number,\n\toptions: AnimationPropertyOptions & { nodeIds?: readonly string[] } = {}\n): { nodes: TNode[]; refs: KeyframeRef[] } {\n\tconst draft = new Draft(nodes, options);\n\tconst t = coerceTime(time);\n\tconst sources = [...new Set(clipboard.keyframes.map((k) => k.nodeId))];\n\tconst targetsOf = (source: string): string[] => {\n\t\tif (!options.nodeIds || options.nodeIds.length === 0) return [source];\n\t\tif (sources.length === 1) return [...options.nodeIds];\n\t\tconst i = sources.indexOf(source);\n\t\treturn i < options.nodeIds.length ? [options.nodeIds[i]] : [];\n\t};\n\tconst refs: KeyframeRef[] = [];\n\tfor (const k of clipboard.keyframes) {\n\t\tconst p = draft.property(k.property);\n\t\tif (!p) continue;\n\t\tfor (const target of targetsOf(k.nodeId)) {\n\t\t\tif (!draft.has(target)) continue;\n\t\t\tlet value: AnimationValue;\n\t\t\ttry {\n\t\t\t\tvalue = coerceValue(p, k.value);\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst at = t + Math.max(0, k.offset);\n\t\t\tputKey(draft, target, k.property, at, value, k.easing, false);\n\t\t\trefs.push({ nodeId: target, property: k.property, time: at });\n\t\t}\n\t}\n\treturn { nodes: draft.commit(), refs };\n}\n\n// -------------------------------------------------------------------------------------------\n// Recording\n\nconst sameValue = (a: AnimationValue | undefined, b: AnimationValue | undefined) =>\n\ttypeof a === 'number' && typeof b === 'number' ? Math.abs(a - b) <= 1e-9 : a === b;\n\nexport interface RecordAnimationOptions extends AnimationPropertyOptions {\n\t/**\n\t * Record mode: changing a property that is not animated yet keys it too (otherwise only\n\t * properties that already have keyframes are keyed and other changes are static edits).\n\t */\n\trecording?: boolean;\n}\n\n/**\n * Turns edits made on the *displayed* (evaluated) nodes into edits of the stored nodes.\n *\n * - `base`: the stored nodes; `before`: what was displayed (`applyAnimation(base, time)`);\n * `after`: the displayed nodes after the edit (e.g. React Flow changes applied to `before`).\n * - A changed property with an enabled track is keyed at `time` and keeps its stored value.\n * - In record mode a changed property without a track gets one: a key at 0 with the previous\n * value (when `time` > 0) and a key at `time`, so it animates from where it was. The stored\n * value stays the previous one.\n * - Everything else (other fields, unanimated properties outside record mode) passes through.\n *\n * Unchanged nodes keep their stored identity; returns the new stored nodes and the keyed refs.\n */\nexport function recordAnimationChanges<TNode extends CanvasNode>(\n\tbase: readonly TNode[],\n\tbefore: readonly TNode[],\n\tafter: readonly TNode[],\n\ttime: number,\n\toptions: RecordAnimationOptions = {}\n): { nodes: TNode[]; keyed: KeyframeRef[] } {\n\tconst properties = (options.properties ?? ANIMATABLE_PROPERTIES).filter((p) => p.key !== 'motionPath');\n\tconst baseById = new Map(base.map((n) => [n.id, n]));\n\tconst beforeById = new Map(before.map((n) => [n.id, n]));\n\tconst t = coerceTime(time);\n\tconst keyed: KeyframeRef[] = [];\n\tconst out = after.map((next) => {\n\t\tconst stored = baseById.get(next.id);\n\t\tconst shown = beforeById.get(next.id);\n\t\tif (!stored) return next;\n\t\tif (shown === next) return stored;\n\t\tconst animation = getNodeAnimation(stored);\n\t\tlet result: CanvasNode = next;\n\t\tlet tracks: Record<string, AnimationTrack> | null = null;\n\t\tconst tracksOf = () => (tracks ??= { ...(animation?.tracks ?? {}) });\n\t\tfor (const p of properties) {\n\t\t\tconst track = getAnimationTrack(stored, p.key);\n\t\t\tconst animated = !!track && track.enabled !== false;\n\t\t\tconst now = p.read(next);\n\t\t\tconst was = shown ? p.read(shown) : undefined;\n\t\t\tconst changed = !!shown && now !== undefined && !sameValue(now, was);\n\t\t\tconst restore = () => {\n\t\t\t\tconst value = p.read(stored);\n\t\t\t\tif (value !== undefined) result = p.write(result, value);\n\t\t\t};\n\t\t\tif (animated) {\n\t\t\t\tif (changed) {\n\t\t\t\t\tconst keys = normalizeKeyframes(track!.keyframes).filter((k) => !sameTime(k.time, t));\n\t\t\t\t\tconst before = [...keys].reverse().find((k) => k.time < t);\n\t\t\t\t\tkeys.push({ time: t, value: now!, ...(before?.easing !== undefined ? { easing: before.easing } : {}) });\n\t\t\t\t\ttracksOf()[p.key] = { ...track!, keyframes: keys.sort((a, b) => a.time - b.time) };\n\t\t\t\t\tkeyed.push({ nodeId: next.id, property: p.key, time: t });\n\t\t\t\t}\n\t\t\t\trestore();\n\t\t\t} else if (changed && options.recording && !track) {\n\t\t\t\tconst keys: AnimationKeyframe[] = [];\n\t\t\t\tif (t > ANIMATION_TIME_EPSILON && was !== undefined) keys.push({ time: 0, value: was });\n\t\t\t\tkeys.push({ time: t, value: now! });\n\t\t\t\ttracksOf()[p.key] = { keyframes: keys };\n\t\t\t\tkeyed.push({ nodeId: next.id, property: p.key, time: t });\n\t\t\t\trestore();\n\t\t\t}\n\t\t}\n\t\tif (tracks) result = withNodeAnimation(result, { ...(animation ?? {}), tracks });\n\t\treturn result as TNode;\n\t});\n\treturn { nodes: out, keyed };\n}\n\n// -------------------------------------------------------------------------------------------\n// One-shot animations (agents, scripts)\n\nexport interface AnimateNodeOptions extends AnimationPropertyOptions {\n\t/** Values at the end (property → value). */\n\tto: Record<string, AnimationValue>;\n\t/** Values at the start (default: what the node shows at `delay`). */\n\tfrom?: Record<string, AnimationValue>;\n\t/** Duration in ms (default 1000). */\n\tduration?: number;\n\t/** Start time in ms (default 0). */\n\tdelay?: number;\n\teasing?: AnimationEasing | string;\n}\n\n/**\n * Animates a node from `from` (or its current values) to `to` over `duration` ms starting at\n * `delay`: two keyframes per property, replacing keys at those times.\n */\nexport function animateNode<TNode extends CanvasNode>(nodes: readonly TNode[], nodeId: string, options: AnimateNodeOptions): { nodes: TNode[]; refs: KeyframeRef[] } {\n\tconst draft = new Draft(nodes, options);\n\tconst node = draft.node(nodeId);\n\tconst start = coerceTime(options.delay ?? 0);\n\tconst duration = options.duration ?? 1000;\n\tif (typeof duration !== 'number' || !Number.isFinite(duration) || duration <= 0) throw new AnimationEditError('invalid_time', 'The duration must be positive.');\n\tconst easing = options.easing === undefined ? undefined : coerceEasing(options.easing);\n\tconst entries = Object.entries(options.to ?? {});\n\tif (entries.length === 0) throw new AnimationEditError('invalid_value', 'Name at least one property to animate.');\n\tconst refs: KeyframeRef[] = [];\n\tconst writes: [string, AnimationValue, AnimationValue][] = [];\n\tfor (const [key, target] of entries) {\n\t\tconst p = draft.property(key);\n\t\tif (!p || (key === 'motionPath' && !getNodeAnimation(node)?.motionPath)) {\n\t\t\tthrow new AnimationEditError('unknown_property', `\"${key}\" cannot be animated${p ? ' without a motion path' : ''}.`);\n\t\t}\n\t\tconst from = options.from?.[key] ?? (key === 'motionPath' ? 0 : getAnimatedValue(node, key, start, options));\n\t\tif (from === undefined) throw new AnimationEditError('invalid_value', `\"${nodeId}\" has no ${key} to animate from; pass \"from\".`);\n\t\twrites.push([key, coerceValue(p, from), coerceValue(p, target)]);\n\t}\n\tfor (const [key, from, to] of writes) {\n\t\tputKey(draft, nodeId, key, start, from, easing);\n\t\tputKey(draft, nodeId, key, start + duration, to, undefined, false);\n\t\trefs.push({ nodeId, property: key, time: start }, { nodeId, property: key, time: start + duration });\n\t}\n\treturn { nodes: draft.commit(), refs };\n}\n","import type { CanvasNode } from '../types.js';\nimport { easingToCss } from './easing.js';\nimport { evaluateAnimation, evaluateTrack, getAnimationDuration, type AnimationDocument, type AnimationLoopMode, type AnimationOverrides, type EvaluateAnimationOptions } from './evaluate.js';\nimport { ANIMATABLE_PROPERTIES, getNodeAnimation, normalizeKeyframes, type AnimationKeyframe, type AnimationValue } from './model.js';\n\nconst nodesOf = (doc: AnimationDocument): readonly CanvasNode[] => (Array.isArray(doc) ? doc : (doc as { nodes: readonly CanvasNode[] }).nodes);\n\nexport interface SampleAnimationOptions extends EvaluateAnimationOptions {\n\t/** Frames per second (default 30). */\n\tfps?: number;\n\t/** Length in ms (default: the last keyframe time). */\n\tduration?: number;\n\t/** First time sampled (default 0). */\n\tstart?: number;\n\t/** Refuses to produce more frames than this (default 100 000). */\n\tmaxFrames?: number;\n}\n\nexport interface AnimationFrame {\n\tindex: number;\n\t/** Time in ms. */\n\ttime: number;\n\toverrides: AnimationOverrides;\n}\n\n/**\n * Samples the animation at a fixed frame rate from `start` through `start + duration`\n * (both included), e.g. to render video frames. Apply a frame with `applyAnimationOverrides`.\n */\nexport function sampleAnimationFrames(doc: AnimationDocument, options: SampleAnimationOptions = {}): AnimationFrame[] {\n\tconst fps = options.fps ?? 30;\n\tif (!(fps > 0) || !Number.isFinite(fps)) throw new RangeError('fps must be a positive number.');\n\tconst start = options.start ?? 0;\n\tconst duration = Math.max(0, options.duration ?? getAnimationDuration(doc) - start);\n\tconst step = 1000 / fps;\n\tconst count = Math.floor(duration / step + 1e-9) + 1;\n\tif (count > (options.maxFrames ?? 100_000)) throw new RangeError(`Sampling would produce ${count} frames.`);\n\tconst frames: AnimationFrame[] = [];\n\tfor (let i = 0; i < count; i++) {\n\t\tconst time = start + i * step;\n\t\tframes.push({ index: i, time, overrides: evaluateAnimation(doc, time, options) });\n\t}\n\treturn frames;\n}\n\n// -------------------------------------------------------------------------------------------\n// CSS / Web Animations\n\n/** CSS property each animatable property maps to (camelCase for the Web Animations API). */\nexport const CSS_ANIMATION_PROPERTIES: Readonly<Record<string, { css: string; camel: string }>> = {\n\tx: { css: 'translate', camel: 'translate' },\n\ty: { css: 'translate', camel: 'translate' },\n\tmotionPath: { css: 'translate', camel: 'translate' },\n\trotation: { css: 'rotate', camel: 'rotate' },\n\tscale: { css: 'scale', camel: 'scale' },\n\topacity: { css: 'opacity', camel: 'opacity' },\n\tfill: { css: 'background-color', camel: 'backgroundColor' },\n\tstroke: { css: 'border-color', camel: 'borderColor' },\n\tstrokeWidth: { css: 'border-width', camel: 'borderWidth' },\n\tcornerRadius: { css: 'border-radius', camel: 'borderRadius' },\n\twidth: { css: 'width', camel: 'width' },\n\theight: { css: 'height', camel: 'height' }\n};\n\nexport interface AnimationStop {\n\t/** 0–1 */\n\toffset: number;\n\tvalue: string;\n\t/** Timing function of the segment starting here. */\n\teasing?: string;\n}\n\nexport interface CssPropertyAnimation {\n\t/** CSS property, e.g. `translate`. */\n\tproperty: string;\n\t/** camelCase name for `element.animate`. */\n\tcamel: string;\n\t/** Animatable properties driving it, e.g. `['x', 'y']`. */\n\tsources: string[];\n\tstops: AnimationStop[];\n\t/** False when the stops are sampled (colours mixed in OKLab, x / y with different timing, motion paths). */\n\texact: boolean;\n}\n\nexport interface CssExportOptions {\n\t/** Timeline length in ms (default: the document's last keyframe time). */\n\tduration?: number;\n\tloop?: AnimationLoopMode;\n\tplaybackRate?: number;\n\t/** Sampling rate for tracks CSS cannot express exactly (default 30 per second). */\n\tsampleFps?: number;\n\t/** Map properties to other CSS properties (e.g. `{ fill: { css: 'fill', camel: 'fill' } }` for SVG). */\n\tcssProperties?: Record<string, { css: string; camel: string }>;\n}\n\nconst round = (v: number) => Math.round(v * 1000) / 1000;\n\nfunction cssValue(source: string, value: AnimationValue, node: CanvasNode, partner?: AnimationValue, partnerSource?: string): string {\n\tif (source === 'x' || source === 'y') {\n\t\tconst dx = source === 'x' ? (value as number) - node.position.x : partner !== undefined ? (partner as number) - node.position.x : 0;\n\t\tconst dy = source === 'y' ? (value as number) - node.position.y : partner !== undefined && partnerSource === 'y' ? (partner as number) - node.position.y : 0;\n\t\treturn `${round(dx)}px ${round(dy)}px`;\n\t}\n\tif (typeof value === 'string') return value;\n\tswitch (source) {\n\t\tcase 'rotation':\n\t\t\treturn `${round(value)}deg`;\n\t\tcase 'scale':\n\t\tcase 'opacity':\n\t\t\treturn String(round(value));\n\t\tdefault:\n\t\t\treturn `${round(value)}px`;\n\t}\n}\n\nconst sameTimes = (a: AnimationKeyframe[], b: AnimationKeyframe[]) =>\n\ta.length === b.length && a.every((k, i) => Math.abs(k.time - b[i].time) < 1e-6 && JSON.stringify(k.easing ?? 'linear') === JSON.stringify(b[i].easing ?? 'linear'));\n\nfunction needsOklab(keys: AnimationKeyframe[]): boolean {\n\tfor (let i = 0; i + 1 < keys.length; i++) {\n\t\tif (keys[i].easing !== 'hold' && keys[i].value !== keys[i + 1].value) return true;\n\t}\n\treturn false;\n}\n\nfunction exactStops(keys: AnimationKeyframe[], duration: number, format: (k: AnimationKeyframe, i: number) => string): AnimationStop[] {\n\tconst stops: AnimationStop[] = [];\n\tif (keys[0].time > 0) stops.push({ offset: 0, value: format(keys[0], 0), easing: 'linear' });\n\tkeys.forEach((k, i) => {\n\t\tconst next = keys[i + 1];\n\t\tstops.push({\n\t\t\toffset: Math.min(1, Math.max(0, k.time / duration)),\n\t\t\tvalue: format(k, i),\n\t\t\t...(next ? { easing: easingToCss(k.easing, next.time - k.time) } : {})\n\t\t});\n\t});\n\tconst last = keys[keys.length - 1];\n\tif (last.time < duration) stops.push({ offset: 1, value: format(last, keys.length - 1) });\n\treturn stops;\n}\n\nfunction sampledStops(duration: number, fps: number, keyTimes: number[], valueAt: (time: number) => string): AnimationStop[] {\n\tconst times = new Set<number>([0, duration, ...keyTimes.filter((t) => t >= 0 && t <= duration)]);\n\tconst step = 1000 / Math.max(1, fps);\n\tfor (let t = step; t < duration; t += step) times.add(t);\n\treturn [...times]\n\t\t.sort((a, b) => a - b)\n\t\t.map((t) => ({ offset: Math.min(1, t / duration), value: valueAt(t), easing: 'linear' }));\n}\n\n/**\n * The node's enabled tracks as per-CSS-property keyframe stops: rotation / scale use the\n * individual transform properties, x / y become `translate` relative to the node's position.\n * Tracks CSS cannot reproduce exactly are sampled (`exact: false`).\n */\nexport function getCssPropertyAnimations(nodes: readonly CanvasNode[], nodeId: string, options: CssExportOptions = {}): CssPropertyAnimation[] {\n\tconst node = nodes.find((n) => n.id === nodeId);\n\tconst animation = getNodeAnimation(node);\n\tif (!node || !animation) return [];\n\tconst duration = options.duration ?? getAnimationDuration([node]);\n\tif (!(duration > 0)) return [];\n\tconst fps = options.sampleFps ?? 30;\n\tconst map = { ...CSS_ANIMATION_PROPERTIES, ...(options.cssProperties ?? {}) };\n\tconst tracks = new Map<string, AnimationKeyframe[]>();\n\tfor (const [key, track] of Object.entries(animation.tracks)) {\n\t\tif (!track || track.enabled === false || !map[key]) continue;\n\t\tconst keys = normalizeKeyframes(track.keyframes);\n\t\tif (keys.length > 0) tracks.set(key, keys);\n\t}\n\tconst typeOf = (key: string) => ANIMATABLE_PROPERTIES.find((p) => p.key === key)?.type ?? 'number';\n\tconst groups = new Map<string, string[]>();\n\tfor (const key of tracks.keys()) {\n\t\tconst css = map[key].css;\n\t\tgroups.set(css, [...(groups.get(css) ?? []), key]);\n\t}\n\tconst out: CssPropertyAnimation[] = [];\n\tfor (const [css, sources] of groups) {\n\t\tconst camel = map[sources[0]].camel;\n\t\tconst keysOf = (k: string) => tracks.get(k)!;\n\t\tconst evalAt = (k: string, t: number) => evaluateTrack(keysOf(k), t, typeOf(k))!;\n\t\tconst allTimes = sources.flatMap((k) => keysOf(k).map((x) => x.time));\n\t\tif (sources.includes('motionPath')) {\n\t\t\t// The motion path track drives the translate; sample its positions.\n\t\t\tconst position = (t: number) => {\n\t\t\t\tconst moved = ANIMATABLE_PROPERTIES.find((p) => p.key === 'motionPath')!.write(node, evalAt('motionPath', t));\n\t\t\t\treturn `${round(moved.position.x - node.position.x)}px ${round(moved.position.y - node.position.y)}px`;\n\t\t\t};\n\t\t\tout.push({ property: css, camel, sources, stops: sampledStops(duration, fps, allTimes, position), exact: false });\n\t\t\tcontinue;\n\t\t}\n\t\tif (sources.length === 2 && css === 'translate') {\n\t\t\tconst [a, b] = sources[0] === 'x' ? sources : [sources[1], sources[0]];\n\t\t\tif (sameTimes(keysOf(a), keysOf(b))) {\n\t\t\t\tconst bKeys = keysOf(b);\n\t\t\t\tout.push({ property: css, camel, sources, stops: exactStops(keysOf(a), duration, (k, i) => cssValue(a, k.value, node, bKeys[i].value, b)), exact: true });\n\t\t\t} else {\n\t\t\t\tout.push({\n\t\t\t\t\tproperty: css,\n\t\t\t\t\tcamel,\n\t\t\t\t\tsources,\n\t\t\t\t\tstops: sampledStops(duration, fps, allTimes, (t) => cssValue(a, evalAt(a, t), node, evalAt(b, t), b)),\n\t\t\t\t\texact: false\n\t\t\t\t});\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tconst key = sources[sources.length - 1];\n\t\tconst keys = keysOf(key);\n\t\tif (typeOf(key) === 'color' && needsOklab(keys)) {\n\t\t\tout.push({ property: css, camel, sources: [key], stops: sampledStops(duration, fps, allTimes, (t) => cssValue(key, evalAt(key, t), node)), exact: false });\n\t\t} else {\n\t\t\tout.push({ property: css, camel, sources: [key], stops: exactStops(keys, duration, (k) => cssValue(key, k.value, node)), exact: true });\n\t\t}\n\t}\n\treturn out;\n}\n\nfunction timing(options: CssExportOptions, duration: number) {\n\tconst rate = options.playbackRate && Number.isFinite(options.playbackRate) && options.playbackRate !== 0 ? options.playbackRate : 1;\n\tconst loop = options.loop ?? 'none';\n\treturn {\n\t\tduration: round(duration / Math.abs(rate)),\n\t\titerations: loop === 'none' ? 1 : Infinity,\n\t\tdirection: (loop === 'ping-pong' ? (rate < 0 ? 'alternate-reverse' : 'alternate') : rate < 0 ? 'reverse' : 'normal') as PlaybackDirectionLike\n\t};\n}\n\ntype PlaybackDirectionLike = 'normal' | 'reverse' | 'alternate' | 'alternate-reverse';\n\nexport interface CssAnimationExport {\n\t/** `@keyframes` blocks plus a rule applying them to `selector`. */\n\tcss: string;\n\t/** Value for the `animation` property (comma-separated, one per CSS property). */\n\tanimation: string;\n\tanimations: { name: string; property: string; exact: boolean }[];\n\t/** Whether every property is reproduced exactly. */\n\texact: boolean;\n}\n\nconst cssIdent = (text: string) => text.replace(/[^A-Za-z0-9_-]/g, '-').replace(/^(\\d)/, '_$1');\n\n/**\n * Exports a node's animation as CSS `@keyframes` (one per CSS property, so different tracks\n * keep their own easing) and an `animation` declaration for `selector`.\n */\nexport function exportCssAnimation(\n\tnodes: readonly CanvasNode[],\n\tnodeId: string,\n\toptions: CssExportOptions & { name?: string; selector?: string } = {}\n): CssAnimationExport {\n\tconst duration = options.duration ?? getAnimationDuration(nodes.filter((n) => n.id === nodeId));\n\tconst props = getCssPropertyAnimations(nodes, nodeId, { ...options, duration });\n\tconst base = cssIdent(options.name ?? `anim-${nodeId}`);\n\tconst t = timing(options, duration);\n\tconst blocks: string[] = [];\n\tconst parts: string[] = [];\n\tconst animations: CssAnimationExport['animations'] = [];\n\tfor (const prop of props) {\n\t\tconst name = `${base}-${cssIdent(prop.property)}`;\n\t\tconst lines = prop.stops.map((s) => {\n\t\t\tconst pct = `${round(s.offset * 100)}%`;\n\t\t\tconst easing = s.easing ? ` animation-timing-function: ${s.easing};` : '';\n\t\t\treturn ` ${pct} { ${prop.property}: ${s.value};${easing} }`;\n\t\t});\n\t\tblocks.push(`@keyframes ${name} {\\n${lines.join('\\n')}\\n}`);\n\t\tparts.push(`${name} ${t.duration}ms linear 0ms ${t.iterations === Infinity ? 'infinite' : 1} ${t.direction} both`);\n\t\tanimations.push({ name, property: prop.property, exact: prop.exact });\n\t}\n\tconst animation = parts.join(', ');\n\tconst selector = options.selector ?? `[data-id=\"${nodeId.replace(/[\"\\\\]/g, '\\\\$&')}\"]`;\n\tconst css = blocks.length ? `${blocks.join('\\n\\n')}\\n\\n${selector} {\\n animation: ${animation};\\n}\\n` : '';\n\treturn { css, animation, animations, exact: props.every((p) => p.exact) };\n}\n\n/** One `element.animate(keyframes, options)` call. */\nexport interface WebAnimationSpec {\n\tproperty: string;\n\tkeyframes: { offset: number; easing?: string; [property: string]: string | number | undefined }[];\n\toptions: { duration: number; iterations: number; direction: PlaybackDirectionLike; fill: 'both'; easing: 'linear' };\n\texact: boolean;\n}\n\n/** The node's animation as Web Animations API keyframes (one animation per CSS property). */\nexport function toWebAnimations(nodes: readonly CanvasNode[], nodeId: string, options: CssExportOptions = {}): WebAnimationSpec[] {\n\tconst duration = options.duration ?? getAnimationDuration(nodes.filter((n) => n.id === nodeId));\n\tconst t = timing(options, duration);\n\treturn getCssPropertyAnimations(nodes, nodeId, { ...options, duration }).map((prop) => ({\n\t\tproperty: prop.property,\n\t\tkeyframes: prop.stops.map((s) => ({ offset: s.offset, [prop.camel]: s.value, ...(s.easing ? { easing: s.easing } : {}) })),\n\t\toptions: { duration: t.duration, iterations: t.iterations, direction: t.direction, fill: 'both', easing: 'linear' },\n\t\texact: prop.exact\n\t}));\n}\n\n/** Plays Web Animations specs on an element (anything with `animate`), returning the animations. */\nexport function playWebAnimations<TAnimation>(\n\telement: { animate(keyframes: any, options: any): TAnimation },\n\tspecs: readonly WebAnimationSpec[]\n): TAnimation[] {\n\treturn specs.map((spec) => element.animate(spec.keyframes, spec.options));\n}\n\n/** Whether every enabled track of the node can be exported without sampling. */\nexport function isCssExportExact(nodes: readonly CanvasNode[], nodeId: string): boolean {\n\treturn getCssPropertyAnimations(nodes, nodeId).every((p) => p.exact);\n}\n\n","import type { CanvasNode } from '../types.js';\nimport type { AnimationEasing } from './easing.js';\nimport type { KeyframeRef } from './edit.js';\nimport {\n\tANIMATABLE_PROPERTIES,\n\tANIMATION_TIME_EPSILON,\n\tgetAnimatedProperties,\n\tgetAnimationTrack,\n\tnormalizeKeyframes,\n\ttype AnimationPropertyOptions,\n\ttype AnimationValue\n} from './model.js';\n\n/**\n * View-model helpers for timeline UIs (React and Svelte render the same rows).\n */\n\nexport interface TimelineKeyframe {\n\tref: KeyframeRef;\n\t/** Time shown (includes the preview offset of a keyframe drag). */\n\ttime: number;\n\tvalue: AnimationValue;\n\teasing?: AnimationEasing;\n\tselected: boolean;\n}\n\nexport interface TimelineRow {\n\t/** `nodeId:property`. */\n\tid: string;\n\tnodeId: string;\n\tnodeLabel: string;\n\tproperty: string;\n\tlabel: string;\n\tenabled: boolean;\n\tkeyframes: TimelineKeyframe[];\n}\n\nexport interface TimelineRowsOptions extends AnimationPropertyOptions {\n\t/** Selected keyframes. */\n\tselection?: readonly KeyframeRef[];\n\t/** Preview offset for the selected keyframes while they are dragged. */\n\tdragDelta?: number;\n\t/** Row label for a node (default: `data.label`, then the id). */\n\tlabelOf?: (node: CanvasNode) => string;\n}\n\n/** Whether `ref` is in `selection` (same node, property and time). */\nexport function isKeyframeSelected(selection: readonly KeyframeRef[], ref: KeyframeRef): boolean {\n\treturn selection.some((s) => s.nodeId === ref.nodeId && s.property === ref.property && Math.abs(s.time - ref.time) <= ANIMATION_TIME_EPSILON);\n}\n\nconst defaultLabel = (node: CanvasNode) => (typeof node.data?.label === 'string' && node.data.label.trim() ? node.data.label : node.id);\n\n/** One row per animated property of each node in `ids` (in `ids` order). */\nexport function getTimelineRows(nodes: readonly CanvasNode[], ids: Iterable<string>, options: TimelineRowsOptions = {}): TimelineRow[] {\n\tconst byId = new Map(nodes.map((n) => [n.id, n]));\n\tconst selection = options.selection ?? [];\n\tconst labelOf = options.labelOf ?? defaultLabel;\n\tconst rows: TimelineRow[] = [];\n\tfor (const id of new Set(ids)) {\n\t\tconst node = byId.get(id);\n\t\tif (!node) continue;\n\t\tfor (const property of getAnimatedProperties(node, options)) {\n\t\t\tconst track = getAnimationTrack(node, property)!;\n\t\t\tconst label = (options.properties ?? ANIMATABLE_PROPERTIES).find((p) => p.key === property)?.label ?? property;\n\t\t\trows.push({\n\t\t\t\tid: `${id}:${property}`,\n\t\t\t\tnodeId: id,\n\t\t\t\tnodeLabel: labelOf(node),\n\t\t\t\tproperty,\n\t\t\t\tlabel,\n\t\t\t\tenabled: track.enabled !== false,\n\t\t\t\tkeyframes: normalizeKeyframes(track.keyframes).map((k) => {\n\t\t\t\t\tconst ref = { nodeId: id, property, time: k.time };\n\t\t\t\t\tconst selected = isKeyframeSelected(selection, ref);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tref,\n\t\t\t\t\t\ttime: selected && options.dragDelta ? k.time + options.dragDelta : k.time,\n\t\t\t\t\t\tvalue: k.value,\n\t\t\t\t\t\t...(k.easing !== undefined ? { easing: k.easing } : {}),\n\t\t\t\t\t\tselected\n\t\t\t\t\t};\n\t\t\t\t})\n\t\t\t});\n\t\t}\n\t}\n\treturn rows;\n}\n\n/** x position (px) of `time` on a lane `width` px wide showing `[0, duration]`. */\nexport function timeToX(time: number, duration: number, width: number): number {\n\treturn duration > 0 ? (time / duration) * width : 0;\n}\n\n/** Time at x (px) on a lane `width` px wide, clamped to `[0, duration]`. */\nexport function xToTime(x: number, duration: number, width: number): number {\n\tif (!(width > 0) || !(duration > 0)) return 0;\n\treturn Math.min(duration, Math.max(0, (x / width) * duration));\n}\n\n/** `1.25s` style label. */\nexport function formatAnimationTime(ms: number): string {\n\tconst s = Math.max(0, ms) / 1000;\n\treturn `${s.toFixed(s < 10 ? 2 : 1)}s`;\n}\n\nconst STEPS = [10, 20, 50, 100, 200, 250, 500, 1000, 2000, 5000, 10_000, 15_000, 30_000, 60_000, 120_000, 300_000, 600_000];\n\n/** Ruler ticks at a round step leaving at least `minSpacing` px between labels. */\nexport function getTimelineTicks(duration: number, width: number, minSpacing = 60): { time: number; x: number; label: string }[] {\n\tif (!(duration > 0) || !(width > 0)) return [];\n\tconst step = STEPS.find((s) => timeToX(s, duration, width) >= minSpacing) ?? STEPS[STEPS.length - 1];\n\tconst ticks: { time: number; x: number; label: string }[] = [];\n\tfor (let t = 0; t <= duration + 1e-6 && ticks.length < 1000; t += step) {\n\t\tticks.push({ time: t, x: timeToX(t, duration, width), label: formatAnimationTime(t) });\n\t}\n\treturn ticks;\n}\n","// Portions ported from filmcraft-main/crates/engine/src/keyframe_move.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport type { Command, MergedExecutionOptions } from '../../history/types.js';\nimport type { Clock } from '../clock.js';\nimport type { CanvasNode } from '../types.js';\nimport type { AnimationEasing } from './easing.js';\nimport {\n\taddKeyframesAtTime,\n\tanimateNode,\n\tcopyKeyframes,\n\tdeleteKeyframes,\n\tgetKeyframeTimes,\n\tmoveKeyframes,\n\tpasteKeyframes,\n\trecordAnimationChanges,\n\tremoveTrack,\n\tscaleKeyframes,\n\tsetKeyframe,\n\tsetKeyframeEasing,\n\tsetMotionPath,\n\tsetTrackEnabled,\n\tsnapKeyframeDelta,\n\tsnapTimeToFrame,\n\tupdateKeyframe,\n\ttype AnimateNodeOptions,\n\ttype AnimationKeyframeClipboard,\n\ttype KeyframePatch,\n\ttype KeyframeRef\n} from './edit.js';\nimport { applyAnimation, getAnimationDuration, type AnimationLoopMode } from './evaluate.js';\nimport { exportCssAnimation, sampleAnimationFrames, toWebAnimations, type CssExportOptions, type SampleAnimationOptions } from './export.js';\nimport { ANIMATION_DATA_KEY, ANIMATION_TIME_EPSILON, getNodeAnimation, normalizeKeyframes, type AnimatableProperty, type AnimationValue } from './model.js';\nimport { AnimationPlayer, type AnimationPlayerState } from './player.js';\nimport { getTimelineRows, isKeyframeSelected, type TimelineRow, type TimelineRowsOptions } from './timeline.js';\n\n/** History surface (`HistoryManager` satisfies it). */\nexport interface AnimationHistoryTarget {\n\texecute(command: Command): void;\n\texecuteMerged?(command: Command, options?: MergedExecutionOptions): void;\n}\n\nexport interface KeyframeDragState {\n\trefs: KeyframeRef[];\n\t/** Offset previewed (after clamping and snapping). */\n\tdelta: number;\n\t/** Time snapped to, for a snap guide. */\n\ttarget: number | null;\n}\n\nexport interface CanvasAnimationState extends AnimationPlayerState {\n\t/** Record mode: changing an unanimated property at the playhead keys it. */\n\trecording: boolean;\n\t/** Frame rate used for frame snapping and export (default 30). */\n\tfps: number;\n\tselection: readonly KeyframeRef[];\n\tdrag: KeyframeDragState | null;\n\t/** Whether the duration follows the last keyframe (`setDuration(null)`). */\n\tautoDuration: boolean;\n}\n\nexport interface CanvasAnimationControllerOptions<TNode extends CanvasNode = CanvasNode> {\n\tgetNodes: () => TNode[];\n\tsetNodes: (nodes: TNode[]) => void;\n\t/** When set, keyframe edits are undoable steps (recording merges a drag into one step). */\n\thistory?: AnimationHistoryTarget;\n\t/** Ids acted on without explicit ids (default: nodes with `selected: true`). */\n\tgetSelectedIds?: () => string[];\n\t/** Time source for playback (default `realClock`). */\n\tclock?: Clock;\n\t/** Fixed timeline length in ms; omitted = the last keyframe time, at least `minDuration`. */\n\tduration?: number;\n\t/** Shortest automatic duration (default 1000 ms). */\n\tminDuration?: number;\n\tloop?: AnimationLoopMode;\n\tplaybackRate?: number;\n\tfps?: number;\n\trecording?: boolean;\n\tproperties?: readonly AnimatableProperty[];\n\t/** Called when the playhead moves. */\n\tonTime?: (time: number) => void;\n\tonChange?: (state: CanvasAnimationState) => void;\n}\n\nconst uniq = <T>(items: Iterable<T>) => [...new Set(items)];\n\n/**\n * Framework-agnostic animation controller shared by the React hook and the Svelte rune:\n * playback (on an injected clock), record mode, keyframe editing with undo, multi-keyframe\n * drag with snapping, copy / paste and export.\n *\n * Render `display()` (the nodes evaluated at the playhead) and route edits made on the\n * displayed nodes through `applyDisplayChanges`, so animated properties are keyed instead of\n * being overwritten by the next frame.\n */\nexport function createCanvasAnimationController<TNode extends CanvasNode = CanvasNode>(options: CanvasAnimationControllerOptions<TNode>) {\n\tlet fixedDuration: number | null = options.duration !== undefined && options.duration > 0 ? options.duration : null;\n\tlet recording = options.recording ?? false;\n\tlet fps = options.fps && options.fps > 0 ? options.fps : 30;\n\tlet selection: KeyframeRef[] = [];\n\tlet drag: KeyframeDragState | null = null;\n\tlet clipboard: AnimationKeyframeClipboard | null = null;\n\tconst listeners = new Set<(state: CanvasAnimationState) => void>();\n\tconst propertyOptions = () => (options.properties ? { properties: options.properties } : {});\n\n\tconst durationOf = (nodes: readonly CanvasNode[]) =>\n\t\tfixedDuration ?? Math.max(options.minDuration ?? 1000, getAnimationDuration(nodes));\n\n\tlet durationNodes: readonly CanvasNode[] | null = null;\n\tlet durationValue = 0;\n\tconst duration = () => {\n\t\tconst nodes = options.getNodes();\n\t\tif (nodes !== durationNodes) {\n\t\t\tdurationNodes = nodes;\n\t\t\tdurationValue = durationOf(nodes);\n\t\t}\n\t\treturn fixedDuration ?? durationValue;\n\t};\n\n\tconst player = new AnimationPlayer({\n\t\tclock: options.clock,\n\t\tduration,\n\t\tloop: options.loop,\n\t\tplaybackRate: options.playbackRate,\n\t\tonTime: (time) => options.onTime?.(time)\n\t});\n\n\tlet cached: CanvasAnimationState | null = null;\n\tconst snapshot = (): CanvasAnimationState => ({\n\t\t...player.getState(),\n\t\tduration: duration(),\n\t\trecording,\n\t\tfps,\n\t\tselection,\n\t\tdrag,\n\t\tautoDuration: fixedDuration === null\n\t});\n\tconst getState = (): CanvasAnimationState => {\n\t\tconst next = snapshot();\n\t\tif (\n\t\t\tcached &&\n\t\t\tcached.time === next.time &&\n\t\t\tcached.playing === next.playing &&\n\t\t\tcached.scrubbing === next.scrubbing &&\n\t\t\tcached.loop === next.loop &&\n\t\t\tcached.playbackRate === next.playbackRate &&\n\t\t\tcached.duration === next.duration &&\n\t\t\tcached.recording === next.recording &&\n\t\t\tcached.fps === next.fps &&\n\t\t\tcached.selection === next.selection &&\n\t\t\tcached.drag === next.drag &&\n\t\t\tcached.autoDuration === next.autoDuration\n\t\t) {\n\t\t\treturn cached;\n\t\t}\n\t\tcached = next;\n\t\treturn cached;\n\t};\n\tconst notify = () => {\n\t\tconst state = getState();\n\t\toptions.onChange?.(state);\n\t\tfor (const listener of listeners) listener(state);\n\t};\n\tplayer.subscribe(notify);\n\n\tconst selectedIds = () => options.getSelectedIds?.() ?? options.getNodes().filter((n) => n.selected).map((n) => n.id);\n\n\t// ----- applying edits -----------------------------------------------------------------\n\n\tconst KEEP_FIELDS = ['selected', 'dragging', 'measured'] as const;\n\tconst replaceNodes = (nodes: TNode[], replacements: Map<string, TNode>, animationOnly = false): TNode[] =>\n\t\tnodes.map((node) => {\n\t\t\tconst replacement = replacements.get(node.id);\n\t\t\tif (!replacement) return node;\n\t\t\tif (animationOnly) {\n\t\t\t\tconst data: Record<string, any> = { ...(node.data ?? {}) };\n\t\t\t\tconst value = replacement.data?.[ANIMATION_DATA_KEY];\n\t\t\t\tif (value === undefined) delete data[ANIMATION_DATA_KEY];\n\t\t\t\telse data[ANIMATION_DATA_KEY] = value;\n\t\t\t\treturn { ...node, data } as TNode;\n\t\t\t}\n\t\t\tconst next: Record<string, any> = { ...replacement };\n\t\t\tfor (const field of KEEP_FIELDS) {\n\t\t\t\tif (field in node) next[field] = node[field];\n\t\t\t\telse delete next[field];\n\t\t\t}\n\t\t\treturn next as TNode;\n\t\t});\n\n\t/** Runs a pure edit as one (optionally undoable) step; returns whether anything changed. */\n\tfunction commit(name: string, edit: (nodes: TNode[]) => TNode[], merge?: { tag: string; windowMs?: number; animationOnly?: boolean }): boolean {\n\t\tconst before = options.getNodes();\n\t\tconst after = edit(before);\n\t\tif (after === before) return false;\n\t\tconst beforeById = new Map(before.map((n) => [n.id, n]));\n\t\tconst undoMap = new Map<string, TNode>();\n\t\tconst redoMap = new Map<string, TNode>();\n\t\tfor (const node of after) {\n\t\t\tconst old = beforeById.get(node.id);\n\t\t\tif (old && old !== node) {\n\t\t\t\tundoMap.set(node.id, old);\n\t\t\t\tredoMap.set(node.id, node);\n\t\t\t}\n\t\t}\n\t\tif (redoMap.size === 0) return false;\n\t\tconst scope = merge?.animationOnly ?? false;\n\t\tconst execute = () => options.setNodes(replaceNodes(options.getNodes(), redoMap, scope));\n\t\tconst undo = () => options.setNodes(replaceNodes(options.getNodes(), undoMap, scope));\n\t\tconst command: Command = { name, execute, undo, ...(merge ? { tag: merge.tag } : {}) };\n\t\tif (!options.history) execute();\n\t\telse if (merge && options.history.executeMerged) options.history.executeMerged(command, { windowMs: merge.windowMs ?? 1000 });\n\t\telse options.history.execute(command);\n\t\treturn true;\n\t}\n\n\t/** Drops selected refs that no longer exist. */\n\tfunction pruneSelection() {\n\t\tif (selection.length === 0) return;\n\t\tconst byId = new Map(options.getNodes().map((n) => [n.id, n]));\n\t\tconst kept = selection.filter((ref) => {\n\t\t\tconst track = getNodeAnimation(byId.get(ref.nodeId))?.tracks[ref.property];\n\t\t\treturn !!track && normalizeKeyframes(track.keyframes).some((k) => Math.abs(k.time - ref.time) <= ANIMATION_TIME_EPSILON);\n\t\t});\n\t\tif (kept.length !== selection.length) selection = kept;\n\t}\n\n\tconst setSelection = (refs: KeyframeRef[]) => {\n\t\tselection = refs.map((r) => ({ nodeId: r.nodeId, property: r.property, time: r.time }));\n\t};\n\n\tlet displayCache: { nodes: readonly TNode[]; time: number; out: TNode[] } | null = null;\n\tfunction display(nodes: readonly TNode[] = options.getNodes()): TNode[] {\n\t\tconst time = player.currentTime;\n\t\tif (displayCache && displayCache.nodes === nodes && displayCache.time === time) return displayCache.out;\n\t\tconst out = applyAnimation(nodes, time, propertyOptions());\n\t\tdisplayCache = { nodes, time, out };\n\t\treturn out;\n\t}\n\n\tconst controller = {\n\t\tplayer,\n\t\tgetState,\n\t\tsubscribe(listener: (state: CanvasAnimationState) => void): () => void {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(listener);\n\t\t\t};\n\t\t},\n\t\t/** Re-reads node-derived state (the automatic duration) and notifies when it changed. */\n\t\trefresh() {\n\t\t\tconst prev = cached;\n\t\t\tif (getState() !== prev) notify();\n\t\t},\n\n\t\t// ----- playback ---------------------------------------------------------------------\n\t\tget time() {\n\t\t\treturn player.currentTime;\n\t\t},\n\t\tget playing() {\n\t\t\treturn player.isPlaying;\n\t\t},\n\t\tget duration() {\n\t\t\treturn duration();\n\t\t},\n\t\tplay: () => player.play(),\n\t\tpause: () => player.pause(),\n\t\ttoggle: () => player.toggle(),\n\t\tstop: () => player.stop(),\n\t\tseek: (time: number) => player.seek(time),\n\t\tscrub: (time: number) => player.scrub(time),\n\t\tendScrub: () => player.endScrub(),\n\t\tsetLoop: (loop: AnimationLoopMode) => player.setLoop(loop),\n\t\tsetPlaybackRate: (rate: number) => player.setPlaybackRate(rate),\n\t\t/** Fixed duration in ms, or `null` to follow the last keyframe. */\n\t\tsetDuration(ms: number | null) {\n\t\t\tfixedDuration = ms !== null && Number.isFinite(ms) && ms > 0 ? ms : null;\n\t\t\tdurationNodes = null;\n\t\t\tplayer.setDuration(duration);\n\t\t\tnotify();\n\t\t},\n\t\tsetFps(value: number) {\n\t\t\tif (!(value > 0) || value === fps) return;\n\t\t\tfps = value;\n\t\t\tnotify();\n\t\t},\n\t\tsetRecording(on: boolean) {\n\t\t\tif (recording === on) return;\n\t\t\trecording = on;\n\t\t\tnotify();\n\t\t},\n\t\ttoggleRecording() {\n\t\t\tcontroller.setRecording(!recording);\n\t\t},\n\t\t/** Seeks to the next keyframe (of the selected nodes, or all) after the playhead. */\n\t\tnextKeyframe(ids: Iterable<string> = selectedIds()): number | null {\n\t\t\tconst list = [...ids];\n\t\t\tconst times = getKeyframeTimes(options.getNodes(), list.length ? { ids: list } : {});\n\t\t\tconst next = times.find((t) => t > player.currentTime + ANIMATION_TIME_EPSILON);\n\t\t\tif (next === undefined) return null;\n\t\t\tplayer.seek(next);\n\t\t\treturn next;\n\t\t},\n\t\tpreviousKeyframe(ids: Iterable<string> = selectedIds()): number | null {\n\t\t\tconst list = [...ids];\n\t\t\tconst times = getKeyframeTimes(options.getNodes(), list.length ? { ids: list } : {});\n\t\t\tconst prev = [...times].reverse().find((t) => t < player.currentTime - ANIMATION_TIME_EPSILON);\n\t\t\tif (prev === undefined) return null;\n\t\t\tplayer.seek(prev);\n\t\t\treturn prev;\n\t\t},\n\n\t\t// ----- display ----------------------------------------------------------------------\n\t\t/** The nodes evaluated at the playhead (same array when nothing is animated). */\n\t\tdisplay,\n\t\t/**\n\t\t * Applies edits made on the displayed nodes: animated properties (and, in record mode,\n\t\t * any property) are keyed at the playhead; other changes go to the stored nodes.\n\t\t * Keying is one undo step per node set, merged while a drag continues.\n\t\t */\n\t\tapplyDisplayChanges(next: TNode[]): KeyframeRef[] {\n\t\t\tconst base = options.getNodes();\n\t\t\tconst shown = display(base);\n\t\t\tconst time = player.currentTime;\n\t\t\tconst { nodes, keyed } = recordAnimationChanges(base, shown, next, time, { recording, ...propertyOptions() });\n\t\t\tif (keyed.length === 0) {\n\t\t\t\tif (nodes !== base) options.setNodes(nodes);\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\t// Static edits apply directly; the keyframes are the undoable part.\n\t\t\tconst ids = new Set(keyed.map((k) => k.nodeId));\n\t\t\tconst baseById = new Map(base.map((n) => [n.id, n]));\n\t\t\tconst staticNodes = nodes.map((n) => {\n\t\t\t\tif (!ids.has(n.id)) return n;\n\t\t\t\tconst old = baseById.get(n.id)?.data?.[ANIMATION_DATA_KEY];\n\t\t\t\tconst data: Record<string, any> = { ...(n.data ?? {}) };\n\t\t\t\tif (old === undefined) delete data[ANIMATION_DATA_KEY];\n\t\t\t\telse data[ANIMATION_DATA_KEY] = old;\n\t\t\t\treturn { ...n, data } as TNode;\n\t\t\t});\n\t\t\tconst animationOf = new Map(nodes.filter((n) => ids.has(n.id)).map((n) => [n.id, n.data?.[ANIMATION_DATA_KEY]]));\n\t\t\toptions.setNodes(staticNodes);\n\t\t\tconst tag = `animation.record:${[...ids].sort().join(',')}`;\n\t\t\tcommit(\n\t\t\t\t'Record Keyframe',\n\t\t\t\t(current) =>\n\t\t\t\t\tcurrent.map((n) => {\n\t\t\t\t\t\tif (!animationOf.has(n.id)) return n;\n\t\t\t\t\t\tconst data: Record<string, any> = { ...(n.data ?? {}) };\n\t\t\t\t\t\tconst value = animationOf.get(n.id);\n\t\t\t\t\t\tif (value === undefined) delete data[ANIMATION_DATA_KEY];\n\t\t\t\t\t\telse data[ANIMATION_DATA_KEY] = value;\n\t\t\t\t\t\treturn { ...n, data } as TNode;\n\t\t\t\t\t}),\n\t\t\t\t{ tag, animationOnly: true }\n\t\t\t);\n\t\t\treturn keyed;\n\t\t},\n\n\t\t// ----- editing ----------------------------------------------------------------------\n\t\t/** Keys the nodes (default: selected) at the playhead; see `addKeyframesAtTime`. */\n\t\taddKeyframe(ids?: Iterable<string>, keys?: readonly string[], time = player.currentTime): KeyframeRef[] {\n\t\t\tlet refs: KeyframeRef[] = [];\n\t\t\tcommit('Add Keyframe', (nodes) => {\n\t\t\t\tconst result = addKeyframesAtTime(nodes, ids ?? selectedIds(), time, { ...propertyOptions(), ...(keys ? { keys } : {}) });\n\t\t\t\trefs = result.refs;\n\t\t\t\treturn result.nodes;\n\t\t\t});\n\t\t\tif (refs.length) setSelection(refs);\n\t\t\tnotify();\n\t\t\treturn refs;\n\t\t},\n\t\tsetKeyframe(nodeId: string, property: string, value: AnimationValue, time = player.currentTime, easing?: AnimationEasing | string) {\n\t\t\tcommit('Set Keyframe', (nodes) => setKeyframe(nodes, nodeId, property, time, value, { ...propertyOptions(), ...(easing !== undefined ? { easing } : {}) }));\n\t\t},\n\t\tupdateKeyframe(ref: KeyframeRef, patch: KeyframePatch) {\n\t\t\tcommit('Edit Keyframe', (nodes) => updateKeyframe(nodes, ref, patch, propertyOptions()));\n\t\t\tif (patch.time !== undefined && isKeyframeSelected(selection, ref)) {\n\t\t\t\tsetSelection(selection.map((s) => (isKeyframeSelected([ref], s) ? { ...s, time: Math.max(0, patch.time!) } : s)));\n\t\t\t}\n\t\t\tpruneSelection();\n\t\t\tnotify();\n\t\t},\n\t\tdeleteKeyframes(refs: readonly KeyframeRef[] = selection): boolean {\n\t\t\tif (refs.length === 0) return false;\n\t\t\tconst changed = commit(refs.length === 1 ? 'Delete Keyframe' : 'Delete Keyframes', (nodes) => deleteKeyframes(nodes, refs, propertyOptions()));\n\t\t\tpruneSelection();\n\t\t\tnotify();\n\t\t\treturn changed;\n\t\t},\n\t\tsetEasing(easing: AnimationEasing | string | null, refs: readonly KeyframeRef[] = selection): boolean {\n\t\t\tif (refs.length === 0) return false;\n\t\t\treturn commit('Set Easing', (nodes) => setKeyframeEasing(nodes, refs, easing, propertyOptions()));\n\t\t},\n\t\tsetTrackEnabled(nodeId: string, property: string, enabled: boolean) {\n\t\t\tcommit(enabled ? 'Enable Track' : 'Disable Track', (nodes) => setTrackEnabled(nodes, nodeId, property, enabled));\n\t\t},\n\t\tremoveTrack(nodeId: string, property: string) {\n\t\t\tcommit('Remove Track', (nodes) => removeTrack(nodes, nodeId, property, { ...propertyOptions(), keepValueAt: player.currentTime }));\n\t\t\tpruneSelection();\n\t\t\tnotify();\n\t\t},\n\t\tsetMotionPath(nodeId: string, motionPath: { d: string; autoRotate?: boolean } | null) {\n\t\t\tcommit('Set Motion Path', (nodes) => setMotionPath(nodes, nodeId, motionPath));\n\t\t},\n\t\t/** Animates a node to `to` (from its current values) over `duration`; see `animateNode`. */\n\t\tanimateNode(nodeId: string, opts: Omit<AnimateNodeOptions, 'properties'>): KeyframeRef[] {\n\t\t\tlet refs: KeyframeRef[] = [];\n\t\t\tcommit('Animate', (nodes) => {\n\t\t\t\tconst result = animateNode(nodes, nodeId, { ...opts, ...propertyOptions() });\n\t\t\t\trefs = result.refs;\n\t\t\t\treturn result.nodes;\n\t\t\t});\n\t\t\treturn refs;\n\t\t},\n\t\t/** Moves keyframes (default: selected) by `delta` ms as one step; returns the delta applied. */\n\t\tmoveKeyframes(delta: number, refs: readonly KeyframeRef[] = selection): number {\n\t\t\tif (refs.length === 0) return 0;\n\t\t\tlet applied = 0;\n\t\t\tlet moved: KeyframeRef[] = [];\n\t\t\tcommit(refs.length === 1 ? 'Move Keyframe' : 'Move Keyframes', (nodes) => {\n\t\t\t\tconst result = moveKeyframes(nodes, refs, delta);\n\t\t\t\tapplied = result.delta;\n\t\t\t\tmoved = result.refs;\n\t\t\t\treturn result.nodes;\n\t\t\t});\n\t\t\tif (applied !== 0 && refs === selection) setSelection(moved);\n\t\t\tnotify();\n\t\t\treturn applied;\n\t\t},\n\t\t/** Scales keyframe spacing (default: selected) around `pivot`; returns the factor applied. */\n\t\tscaleKeyframes(factor: number, refs: readonly KeyframeRef[] = selection, pivot?: number): number {\n\t\t\tif (refs.length === 0) return 1;\n\t\t\tlet applied = 1;\n\t\t\tlet scaled: KeyframeRef[] = [];\n\t\t\tcommit('Scale Keyframes', (nodes) => {\n\t\t\t\tconst result = scaleKeyframes(nodes, refs, factor, pivot === undefined ? {} : { pivot });\n\t\t\t\tapplied = result.factor;\n\t\t\t\tscaled = result.refs;\n\t\t\t\treturn result.nodes;\n\t\t\t});\n\t\t\tif (applied !== 1 && refs === selection) setSelection(scaled);\n\t\t\tnotify();\n\t\t\treturn applied;\n\t\t},\n\t\tcopyKeyframes(refs: readonly KeyframeRef[] = selection): AnimationKeyframeClipboard | null {\n\t\t\tif (refs.length === 0) return null;\n\t\t\tclipboard = copyKeyframes(options.getNodes(), refs);\n\t\t\treturn clipboard;\n\t\t},\n\t\t/** Pastes at the playhead onto the selected nodes (or the copied ones). */\n\t\tpasteKeyframes(data: AnimationKeyframeClipboard | null = clipboard, nodeIds: readonly string[] = selectedIds(), time = player.currentTime): KeyframeRef[] {\n\t\t\tif (!data) return [];\n\t\t\tlet refs: KeyframeRef[] = [];\n\t\t\tcommit('Paste Keyframes', (nodes) => {\n\t\t\t\tconst result = pasteKeyframes(nodes, data, time, { ...propertyOptions(), nodeIds });\n\t\t\t\trefs = result.refs;\n\t\t\t\treturn result.nodes;\n\t\t\t});\n\t\t\tif (refs.length) setSelection(refs);\n\t\t\tnotify();\n\t\t\treturn refs;\n\t\t},\n\t\tget clipboard() {\n\t\t\treturn clipboard;\n\t\t},\n\n\t\t// ----- selection & drag -------------------------------------------------------------\n\t\tget selection() {\n\t\t\treturn selection as readonly KeyframeRef[];\n\t\t},\n\t\tselectKeyframes(refs: readonly KeyframeRef[], mode: 'replace' | 'add' | 'toggle' = 'replace') {\n\t\t\tif (mode === 'replace') setSelection([...refs]);\n\t\t\telse if (mode === 'add') setSelection([...selection, ...refs.filter((r) => !isKeyframeSelected(selection, r))]);\n\t\t\telse {\n\t\t\t\tconst removed = selection.filter((s) => !isKeyframeSelected(refs, s));\n\t\t\t\tsetSelection([...removed, ...refs.filter((r) => !isKeyframeSelected(selection, r))]);\n\t\t\t}\n\t\t\tnotify();\n\t\t},\n\t\tclearSelection() {\n\t\t\tif (selection.length === 0) return;\n\t\t\tselection = [];\n\t\t\tnotify();\n\t\t},\n\t\t/** Starts dragging keyframes (default: the selection; refs not selected become the selection). */\n\t\tbeginKeyframeDrag(refs?: readonly KeyframeRef[]) {\n\t\t\tif (refs && !refs.every((r) => isKeyframeSelected(selection, r))) setSelection([...refs]);\n\t\t\tif (selection.length === 0) return;\n\t\t\tdrag = { refs: selection.map((r) => ({ ...r })), delta: 0, target: null };\n\t\t\tnotify();\n\t\t},\n\t\t/**\n\t\t * Previews the drag at `delta` ms. With `snap` (default on) the group snaps to other\n\t\t * keyframes, the playhead, 0 and the end within `thresholdMs`, else to whole frames.\n\t\t */\n\t\tupdateKeyframeDrag(delta: number, opts: { snap?: boolean; thresholdMs?: number } = {}): KeyframeDragState | null {\n\t\t\tif (!drag) return null;\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst times = drag.refs.map((r) => r.time);\n\t\t\tconst minTime = Math.min(...times);\n\t\t\tlet d = Math.max(-minTime, Number.isFinite(delta) ? delta : 0);\n\t\t\tlet target: number | null = null;\n\t\t\tif (opts.snap !== false) {\n\t\t\t\tconst targets = uniq([...getKeyframeTimes(nodes, { exclude: drag.refs }), player.currentTime, 0, duration()]);\n\t\t\t\tconst snapped = snapKeyframeDelta(times, d, targets, opts.thresholdMs ?? 1000 / fps / 2);\n\t\t\t\ttarget = snapped.target;\n\t\t\t\td = snapped.delta;\n\t\t\t\tif (target === null) d = snapTimeToFrame(minTime + d, fps) - minTime;\n\t\t\t}\n\t\t\td = Math.max(-minTime, d);\n\t\t\tif (d !== drag.delta || target !== drag.target) {\n\t\t\t\tdrag = { ...drag, delta: d, target };\n\t\t\t\tnotify();\n\t\t\t}\n\t\t\treturn drag;\n\t\t},\n\t\t/** Commits the drag as one undo step. */\n\t\tendKeyframeDrag(): number {\n\t\t\tif (!drag) return 0;\n\t\t\tconst { refs, delta } = drag;\n\t\t\tdrag = null;\n\t\t\tlet applied = 0;\n\t\t\tif (delta !== 0) {\n\t\t\t\tlet moved: KeyframeRef[] = [];\n\t\t\t\tcommit(refs.length === 1 ? 'Move Keyframe' : 'Move Keyframes', (nodes) => {\n\t\t\t\t\tconst result = moveKeyframes(nodes, refs, delta);\n\t\t\t\t\tapplied = result.delta;\n\t\t\t\t\tmoved = result.refs;\n\t\t\t\t\treturn result.nodes;\n\t\t\t\t});\n\t\t\t\tif (applied !== 0) setSelection(moved);\n\t\t\t}\n\t\t\tnotify();\n\t\t\treturn applied;\n\t\t},\n\t\t/** Cancels the drag preview (Escape); nothing is edited. */\n\t\tcancelKeyframeDrag() {\n\t\t\tif (!drag) return;\n\t\t\tdrag = null;\n\t\t\tnotify();\n\t\t},\n\n\t\t// ----- views & export ---------------------------------------------------------------\n\t\t/** Timeline rows for `ids` (default: selected nodes), including the drag preview. */\n\t\ttimelineRows(ids: Iterable<string> = selectedIds(), opts: Omit<TimelineRowsOptions, 'selection' | 'dragDelta'> = {}): TimelineRow[] {\n\t\t\treturn getTimelineRows(options.getNodes(), ids, { ...propertyOptions(), ...opts, selection, dragDelta: drag?.delta ?? 0 });\n\t\t},\n\t\tsampleFrames(opts: SampleAnimationOptions = {}) {\n\t\t\treturn sampleAnimationFrames(options.getNodes(), { fps, duration: duration(), ...propertyOptions(), ...opts });\n\t\t},\n\t\texportCss(nodeId: string, opts: CssExportOptions & { name?: string; selector?: string } = {}) {\n\t\t\tconst state = player.getState();\n\t\t\treturn exportCssAnimation(options.getNodes(), nodeId, { duration: duration(), loop: state.loop, playbackRate: state.playbackRate, ...opts });\n\t\t},\n\t\ttoWebAnimations(nodeId: string, opts: CssExportOptions = {}) {\n\t\t\tconst state = player.getState();\n\t\t\treturn toWebAnimations(options.getNodes(), nodeId, { duration: duration(), loop: state.loop, playbackRate: state.playbackRate, ...opts });\n\t\t},\n\t\tdestroy() {\n\t\t\tplayer.destroy();\n\t\t\tlisteners.clear();\n\t\t}\n\t};\n\treturn controller;\n}\n\nexport type CanvasAnimationController<TNode extends CanvasNode = CanvasNode> = ReturnType<typeof createCanvasAnimationController<TNode>>;\n","// Portions ported from figcraft/crates/engine/src/components.rs and figcraft/crates/engine/src/cmd/component.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport { applyNodeUpdates, getAbsolutePosition, getNodeSize, readStyleSize, type CanvasNodeUpdate } from './arrange.js';\nimport { applyConstraints, getAutoLayout, getChildLayout, getLayoutSizing, layoutUpMany, type AutoLayoutOptions } from './autoLayout.js';\nimport {\n\tgetCanvasTokenProperty,\n\tparseNodeStyleDeclarations,\n\treadNodeStyleProperty,\n\tresolveCanvasTokens,\n\twriteNodeStyleProperty,\n\ttype CanvasTokenOptions\n} from './tokenBindings.js';\nimport type { TokenState } from '../tokens/index.js';\nimport type { CanvasNode, XYPosition } from './types.js';\n\n/**\n * Figma-style components and instances on the canvas.\n *\n * - A **main component** is a node with `data.component` (a frame whose children, via\n * `parentId`, are its layers). A **component set** (`data.componentSet`) holds variants:\n * components with `data.component.variant` (`{ Size: 'Small', Style: 'Primary' }`).\n * - An **instance** is a node with `data.instance = { componentId, properties }`. Its layers are\n * real nodes copied from the main; every copy records the main layer it mirrors in\n * `data.componentSource` and the property groups changed locally in\n * `data.componentOverrides` (`data.label`, `style.backgroundColor`, `shape.fill`,\n * `layout.gap`, `token.fill`, `hidden`, …).\n * - When a main changes, its instances are rebuilt from it, keeping layer ids and re-applying\n * overrides (matched by source, then by layer name path, so switching variants keeps\n * compatible overrides). Component properties (boolean → visibility, text → a `data` field,\n * instance swap → a nested instance's main) are applied through `data.componentPropertyRefs`\n * on the main's layers.\n * - Instances never gain or lose layers: deleting a layer inside an instance hides it, and\n * nodes added inside one move out next to it. Geometry of layers inside an instance follows\n * the main (and constraints / auto layout); only the instance itself resizes.\n * - An instance whose main was deleted keeps its layers (\"missing main\") and can restore it.\n *\n * Everything lives in `data`, so components and instances survive JSON, copy / paste and\n * duplication; `syncCanvasComponents(before, after)` relinks what a paste brought in.\n */\n\n/** Data key of a main component's definition. */\nexport const COMPONENT_DATA_KEY = 'component';\n/** Data key of a component set's definition (its variants are its child components). */\nexport const COMPONENT_SET_DATA_KEY = 'componentSet';\n/** Data key of an instance's link to its main (`componentId`) and its property values. */\nexport const INSTANCE_DATA_KEY = 'instance';\n/** Data key of the main-component layer an instance layer mirrors. */\nexport const COMPONENT_SOURCE_DATA_KEY = 'componentSource';\n/** Data key of the override groups of an instance layer. */\nexport const COMPONENT_OVERRIDES_DATA_KEY = 'componentOverrides';\n/** Data key of a main layer's component property bindings (field → property name). */\nexport const COMPONENT_PROPERTY_REFS_DATA_KEY = 'componentPropertyRefs';\n\n/** Nesting limit for instances inside instances. */\nconst MAX_DEPTH = 16;\n\nexport type CanvasComponentPropertyType = 'boolean' | 'text' | 'instanceSwap';\n\nexport type CanvasComponentPropertyDef =\n\t| { type: 'boolean'; default: boolean; description?: string }\n\t| { type: 'text'; default: string; description?: string }\n\t| { type: 'instanceSwap'; default: string; preferred?: string[]; description?: string };\n\nexport type CanvasComponentPropertyValue = boolean | string;\n\nexport interface CanvasComponentData {\n\tdescription?: string;\n\t/** Component properties (on a lone component; variants share their set's). */\n\tproperties?: Record<string, CanvasComponentPropertyDef>;\n\t/** Variant property values of a component inside a set. */\n\tvariant?: Record<string, string>;\n}\n\nexport interface CanvasComponentSetData {\n\tdescription?: string;\n\t/** Component properties shared by every variant. */\n\tproperties?: Record<string, CanvasComponentPropertyDef>;\n}\n\nexport interface CanvasInstanceData {\n\tcomponentId: string;\n\tproperties?: Record<string, CanvasComponentPropertyValue>;\n}\n\n/**\n * A field a component property drives on a main layer: `visible` (boolean), `component` (instance\n * swap, on a nested instance) or a `data.<key>` field (text, e.g. `data.label`).\n */\nexport type CanvasComponentPropertyField = 'visible' | 'component' | `data.${string}`;\n\nexport type CanvasComponentErrorCode =\n\t| 'not-found'\n\t| 'invalid'\n\t| 'cycle'\n\t| 'inside-instance'\n\t| 'nested-instance'\n\t| 'missing-main'\n\t| 'no-overrides'\n\t| 'unknown-property'\n\t| 'type-mismatch';\n\nexport type CanvasComponentResult<TNode extends CanvasNode = CanvasNode> =\n\t| { ok: true; nodes: TNode[]; ids: string[] }\n\t| { ok: false; nodes: TNode[]; code: CanvasComponentErrorCode; reason: string };\n\nexport interface CanvasComponentSyncOptions {\n\t/** Ids for new layer copies (default `<instanceId>:<sourceId>`, made unique). */\n\tidGenerator?: (instanceId: string, sourceId: string) => string;\n\t/** Node types whose overrides all survive variant switches (default `['text']`). */\n\ttextTypes?: readonly string[];\n\t/** Re-run auto layout inside rebuilt instances (default `true`). */\n\tlayout?: boolean | AutoLayoutOptions;\n\t/**\n\t * Resolves token bindings after a sync when `state` is given (bindings inside instances\n\t * resolve in the instance's own modes). Bindings are always compared, never values.\n\t */\n\ttokens?: CanvasTokenOptions & { state?: TokenState };\n}\n\nexport interface CreateCanvasComponentOptions extends CanvasComponentSyncOptions {\n\t/** Id of the wrapping frame (when the selection is wrapped). */\n\tid?: string;\n\t/** Node type of the wrapping frame (default `group`). */\n\tframeType?: string;\n\t/** Node types that become a component themselves instead of being wrapped (default `['group', 'frame']`). */\n\tframeTypes?: readonly string[];\n\t/** Name (`data.label`) of a wrapping frame. */\n\tname?: string;\n}\n\nexport interface CreateCanvasInstanceOptions extends CanvasComponentSyncOptions {\n\tid?: string;\n\t/** Frame to place the instance in (must not be inside its own main or an instance). */\n\tparentId?: string;\n\t/** Position in the parent's space (default: right of the main, or 0,0 in a parent). */\n\tposition?: XYPosition;\n\t/** Property values (variant properties pick the variant). */\n\tproperties?: Record<string, CanvasComponentPropertyValue>;\n}\n\n// ---------------------------------------------------------------------------------------------\n// Small helpers\n\ntype Data = Record<string, any>;\ntype Value = CanvasComponentPropertyValue;\n\nconst isRecord = (v: unknown): v is Record<string, any> => typeof v === 'object' && v !== null && !Array.isArray(v);\nconst dataOf = (node: CanvasNode | undefined | null): Data => (isRecord(node?.data) ? (node!.data as Data) : {});\n\n/** Structural equality of JSON-like values (`undefined` members are ignored). */\nfunction same(a: unknown, b: unknown): boolean {\n\tif (a === b) return true;\n\tif (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) {\n\t\treturn typeof a === 'number' && typeof b === 'number' && Number.isNaN(a) && Number.isNaN(b);\n\t}\n\tif (Array.isArray(a) !== Array.isArray(b)) return false;\n\tif (Array.isArray(a)) {\n\t\tconst other = b as unknown[];\n\t\treturn a.length === other.length && a.every((x, i) => same(x, other[i]));\n\t}\n\tconst ra = a as Record<string, unknown>;\n\tconst rb = b as Record<string, unknown>;\n\tconst ka = Object.keys(ra).filter((k) => ra[k] !== undefined);\n\tconst kb = Object.keys(rb).filter((k) => rb[k] !== undefined);\n\treturn ka.length === kb.length && ka.every((k) => same(ra[k], rb[k]));\n}\n\nfunction stringRecord(value: unknown): Record<string, string> {\n\tconst out: Record<string, string> = {};\n\tif (isRecord(value)) for (const [k, v] of Object.entries(value)) if (typeof v === 'string') out[k] = v;\n\treturn out;\n}\n\n/** Whether `node` is a main component. */\nexport function isCanvasComponent(node: CanvasNode | undefined | null): boolean {\n\treturn isRecord(dataOf(node)[COMPONENT_DATA_KEY]);\n}\n\n/** Whether `node` is a component set. */\nexport function isCanvasComponentSet(node: CanvasNode | undefined | null): boolean {\n\treturn isRecord(dataOf(node)[COMPONENT_SET_DATA_KEY]);\n}\n\n/** Whether `node` is an instance (top-level or nested). */\nexport function isCanvasInstance(node: CanvasNode | undefined | null): boolean {\n\tconst inst = dataOf(node)[INSTANCE_DATA_KEY];\n\treturn isRecord(inst) && typeof inst.componentId === 'string';\n}\n\n/** The instance data of `node`, if it is an instance. */\nexport function getInstanceData(node: CanvasNode | undefined | null): CanvasInstanceData | undefined {\n\treturn isCanvasInstance(node) ? (dataOf(node)[INSTANCE_DATA_KEY] as CanvasInstanceData) : undefined;\n}\n\n/** The component definition of `node`, if it is a main component. */\nexport function getComponentData(node: CanvasNode | undefined | null): CanvasComponentData | undefined {\n\treturn isCanvasComponent(node) ? (dataOf(node)[COMPONENT_DATA_KEY] as CanvasComponentData) : undefined;\n}\n\n/** The main-component layer an instance layer mirrors. */\nexport function getComponentSourceId(node: CanvasNode | undefined | null): string | undefined {\n\tconst s = dataOf(node)[COMPONENT_SOURCE_DATA_KEY];\n\treturn typeof s === 'string' ? s : undefined;\n}\n\n/** The override groups of an instance layer. */\nexport function getNodeOverrides(node: CanvasNode | undefined | null): readonly string[] {\n\tconst o = dataOf(node)[COMPONENT_OVERRIDES_DATA_KEY];\n\treturn Array.isArray(o) ? o.filter((g): g is string => typeof g === 'string') : [];\n}\n\n/** Component property bindings of a main layer (field → property name). */\nexport function getComponentPropertyRefs(node: CanvasNode | undefined | null): Readonly<Record<string, string>> {\n\treturn stringRecord(dataOf(node)[COMPONENT_PROPERTY_REFS_DATA_KEY]);\n}\n\n/** A layer's display name: `data.name`, then `data.label`, then `data.text`, then its type. */\nexport function getLayerName(node: CanvasNode | undefined | null): string {\n\tconst d = dataOf(node);\n\tfor (const key of ['name', 'label', 'text']) if (typeof d[key] === 'string' && d[key]) return d[key];\n\treturn typeof node?.type === 'string' ? node.type : '';\n}\n\nfunction withData<T extends CanvasNode>(node: T, data: Data): T {\n\treturn { ...node, data } as T;\n}\n\nfunction setOverrides<T extends CanvasNode>(node: T, groups: Iterable<string>): T {\n\tconst list = [...new Set(groups)].sort();\n\tconst data = { ...dataOf(node) };\n\tif (list.length) data[COMPONENT_OVERRIDES_DATA_KEY] = list;\n\telse delete data[COMPONENT_OVERRIDES_DATA_KEY];\n\treturn same(getNodeOverrides(node), list) ? node : withData(node, data);\n}\n\n/** Removes the component link keys (source, overrides, property refs) from a node's data. */\nfunction unlinked<T extends CanvasNode>(node: T, keepRefs = false): T {\n\tconst d = dataOf(node);\n\tif (!(COMPONENT_SOURCE_DATA_KEY in d) && !(COMPONENT_OVERRIDES_DATA_KEY in d) && (keepRefs || !(COMPONENT_PROPERTY_REFS_DATA_KEY in d))) return node;\n\tconst data = { ...d };\n\tdelete data[COMPONENT_SOURCE_DATA_KEY];\n\tdelete data[COMPONENT_OVERRIDES_DATA_KEY];\n\tif (!keepRefs) delete data[COMPONENT_PROPERTY_REFS_DATA_KEY];\n\treturn withData(node, data);\n}\n\n// ---------------------------------------------------------------------------------------------\n// Override groups: property paths of a node\n\nconst LINK_KEYS = new Set([\n\tCOMPONENT_DATA_KEY,\n\tCOMPONENT_SET_DATA_KEY,\n\tINSTANCE_DATA_KEY,\n\tCOMPONENT_SOURCE_DATA_KEY,\n\tCOMPONENT_OVERRIDES_DATA_KEY,\n\tCOMPONENT_PROPERTY_REFS_DATA_KEY\n]);\nconst NESTED_KEYS: Record<string, string> = { shape: 'shape', layout: 'layout', tokenBindings: 'token', tokenModes: 'tokenMode' };\nconst NESTED_PREFIX: Record<string, string> = { shape: 'shape', layout: 'layout', token: 'tokenBindings', tokenMode: 'tokenModes' };\n/** Node fields that are structure, runtime state or handled by their own group. */\nconst SKIPPED_NODE_KEYS = new Set([\n\t'id',\n\t'parentId',\n\t'parentNode',\n\t'position',\n\t'positionAbsolute',\n\t'selected',\n\t'dragging',\n\t'resizing',\n\t'measured',\n\t'width',\n\t'height',\n\t'style',\n\t'data',\n\t'hidden',\n\t'internals',\n\t'computed',\n\t'handleBounds'\n]);\nconst RUNTIME_KEYS = ['selected', 'dragging', 'resizing'] as const;\nconst UNITLESS = new Set(['opacity', 'zIndex', 'fontWeight', 'lineHeight', 'flex', 'flexGrow', 'flexShrink', 'order', 'zoom']);\n\n/** Groups an instance root keeps as its own (placement, visibility, lock, modes); never overrides. */\nconst OWN_ROOT_GROUPS = new Set([\n\t'position',\n\t'hidden',\n\t'component',\n\t'props',\n\t'data.locked',\n\t'data.lockedPrevious',\n\t'data.childLayout',\n\t'node.draggable',\n\t'node.selectable',\n\t'node.deletable',\n\t'node.connectable',\n\t'node.zIndex',\n\t'node.extent',\n\t'node.expandParent'\n]);\nconst isOwnRootGroup = (g: string) => OWN_ROOT_GROUPS.has(g) || g.startsWith('tokenMode.');\n\nconst camel = (name: string) => (name.startsWith('--') ? name : name.trim().toLowerCase().replace(/-([a-z])/g, (_, c: string) => c.toUpperCase()));\n\nfunction styleKeys(style: CanvasNode['style']): string[] {\n\tif (!style) return [];\n\tif (typeof style === 'object') return Object.keys(style).filter((k) => style[k] !== undefined);\n\treturn parseNodeStyleDeclarations(style).map(([k]) => camel(k));\n}\n\nfunction sizeOf(node: CanvasNode) {\n\tconst s = readStyleSize(node.style);\n\treturn { width: node.width, height: node.height, styleWidth: s.width, styleHeight: s.height };\n}\n\nconst splitGroup = (group: string): [string, string] => {\n\tconst dot = group.indexOf('.');\n\treturn dot < 0 ? [group, ''] : [group.slice(0, dot), group.slice(dot + 1)];\n};\n\n/** Reads one override group (`hidden`, `size`, `position`, `style.<prop>`, `data.<key>`, …) of a node. */\nexport function readOverrideGroup(node: CanvasNode, group: string): unknown {\n\tconst data = dataOf(node);\n\tswitch (group) {\n\t\tcase 'hidden':\n\t\t\treturn node.hidden === true;\n\t\tcase 'position':\n\t\t\treturn node.position;\n\t\tcase 'size':\n\t\t\treturn sizeOf(node);\n\t\tcase 'component':\n\t\t\treturn getInstanceData(node)?.componentId;\n\t\tcase 'props':\n\t\t\treturn getInstanceData(node)?.properties ?? {};\n\t}\n\tconst [kind, key] = splitGroup(group);\n\tswitch (kind) {\n\t\tcase 'style':\n\t\t\treturn readNodeStyleProperty(node.style, key);\n\t\tcase 'data':\n\t\t\treturn data[key];\n\t\tcase 'node':\n\t\t\treturn (node as Data)[key];\n\t}\n\tconst holder = NESTED_PREFIX[kind];\n\tif (holder) return isRecord(data[holder]) ? data[holder][key] : undefined;\n\treturn undefined;\n}\n\nfunction withSize<T extends CanvasNode>(node: T, value: unknown): T {\n\tconst v = (isRecord(value) ? value : {}) as ReturnType<typeof sizeOf>;\n\tconst next = { ...node } as Data;\n\tif (v.width === undefined) delete next.width;\n\telse next.width = v.width;\n\tif (v.height === undefined) delete next.height;\n\telse next.height = v.height;\n\tconst current = readStyleSize(node.style);\n\tlet style = node.style;\n\tif (v.styleWidth !== current.width) style = writeNodeStyleProperty(style, 'width', v.styleWidth);\n\tif (v.styleHeight !== current.height) style = writeNodeStyleProperty(style, 'height', v.styleHeight);\n\tif (style !== node.style) next.style = style;\n\tconst w = v.width ?? v.styleWidth;\n\tconst h = v.height ?? v.styleHeight;\n\tif (node.measured && (w !== undefined || h !== undefined)) {\n\t\tnext.measured = { ...node.measured, ...(w !== undefined ? { width: w } : {}), ...(h !== undefined ? { height: h } : {}) };\n\t}\n\treturn next as T;\n}\n\n/** Returns `node` with one override group set to `value` (the same node when nothing changes). */\nexport function writeOverrideGroup<T extends CanvasNode>(node: T, group: string, value: unknown): T {\n\tif (same(readOverrideGroup(node, group), value)) return node;\n\tconst next = { ...node } as Data;\n\tconst data = { ...dataOf(node) };\n\tswitch (group) {\n\t\tcase 'hidden':\n\t\t\tif (value) next.hidden = true;\n\t\t\telse delete next.hidden;\n\t\t\treturn next as T;\n\t\tcase 'position':\n\t\t\tif (isRecord(value)) next.position = { x: value.x, y: value.y };\n\t\t\treturn next as T;\n\t\tcase 'size':\n\t\t\treturn withSize(node, value);\n\t\tcase 'component': {\n\t\t\tconst inst = getInstanceData(node);\n\t\t\tif (!inst || typeof value !== 'string') return node;\n\t\t\tdata[INSTANCE_DATA_KEY] = { ...inst, componentId: value };\n\t\t\treturn withData(node, data);\n\t\t}\n\t\tcase 'props': {\n\t\t\tconst inst = getInstanceData(node);\n\t\t\tif (!inst) return node;\n\t\t\tdata[INSTANCE_DATA_KEY] = { ...inst, properties: isRecord(value) ? { ...value } : {} };\n\t\t\treturn withData(node, data);\n\t\t}\n\t}\n\tconst [kind, key] = splitGroup(group);\n\tif (!key) return node;\n\tswitch (kind) {\n\t\tcase 'style': {\n\t\t\tconst unitless = typeof value === 'number' && UNITLESS.has(key);\n\t\t\tnext.style = writeNodeStyleProperty(node.style, key, value as string | number | undefined, unitless);\n\t\t\treturn next as T;\n\t\t}\n\t\tcase 'data':\n\t\t\tif (LINK_KEYS.has(key) || key in NESTED_KEYS) return node;\n\t\t\tif (value === undefined) delete data[key];\n\t\t\telse data[key] = value;\n\t\t\treturn withData(node, data);\n\t\tcase 'node':\n\t\t\tif (SKIPPED_NODE_KEYS.has(key)) return node;\n\t\t\tif (value === undefined) delete next[key];\n\t\t\telse next[key] = value;\n\t\t\treturn next as T;\n\t}\n\tconst holder = NESTED_PREFIX[kind];\n\tif (!holder) return node;\n\tconst record = isRecord(data[holder]) ? { ...data[holder] } : {};\n\tif (value === undefined) delete record[key];\n\telse record[key] = value;\n\tif (Object.keys(record).length === 0 && (holder === 'tokenBindings' || holder === 'tokenModes')) delete data[holder];\n\telse data[holder] = record;\n\treturn withData(node, data);\n}\n\nfunction listGroups(node: CanvasNode, out: Set<string>) {\n\tout.add('hidden');\n\tout.add('size');\n\tout.add('position');\n\tfor (const k of styleKeys(node.style)) if (k !== 'width' && k !== 'height') out.add(`style.${k}`);\n\tconst data = dataOf(node);\n\tfor (const [k, v] of Object.entries(data)) {\n\t\tif (LINK_KEYS.has(k)) continue;\n\t\tconst prefix = NESTED_KEYS[k];\n\t\tif (prefix) {\n\t\t\tif (isRecord(v)) for (const sub of Object.keys(v)) out.add(`${prefix}.${sub}`);\n\t\t\tcontinue;\n\t\t}\n\t\tout.add(`data.${k}`);\n\t}\n\tif (isCanvasInstance(node)) {\n\t\tout.add('component');\n\t\tout.add('props');\n\t}\n\tfor (const k of Object.keys(node)) if (!SKIPPED_NODE_KEYS.has(k)) out.add(`node.${k}`);\n}\n\n/** Override groups whose values differ between two nodes. */\nexport function diffOverrideGroups(a: CanvasNode, b: CanvasNode): string[] {\n\tconst groups = new Set<string>();\n\tlistGroups(a, groups);\n\tlistGroups(b, groups);\n\treturn [...groups].filter((g) => !same(readOverrideGroup(a, g), readOverrideGroup(b, g)));\n}\n\nfunction copyGroups<T extends CanvasNode>(from: CanvasNode, to: T, groups: Iterable<string>): T {\n\tlet next = to;\n\tfor (const g of groups) next = writeOverrideGroup(next, g, readOverrideGroup(from, g));\n\treturn next;\n}\n\n/** The group a component property field drives. */\nfunction fieldGroup(field: string): string {\n\treturn field === 'visible' ? 'hidden' : field;\n}\n\n// ---------------------------------------------------------------------------------------------\n// Tree\n\nclass Graph {\n\treadonly byId: Map<string, CanvasNode>;\n\tprivate readonly kids = new Map<string, string[]>();\n\n\tconstructor(readonly nodes: readonly CanvasNode[]) {\n\t\tthis.byId = new Map(nodes.map((n) => [n.id, n]));\n\t\tfor (const n of nodes) {\n\t\t\tif (!n.parentId || !this.byId.has(n.parentId)) continue;\n\t\t\tlet list = this.kids.get(n.parentId);\n\t\t\tif (!list) this.kids.set(n.parentId, (list = []));\n\t\t\tlist.push(n.id);\n\t\t}\n\t}\n\n\tget(id: string | undefined): CanvasNode | undefined {\n\t\treturn id === undefined ? undefined : this.byId.get(id);\n\t}\n\n\tchildren(id: string): readonly string[] {\n\t\treturn this.kids.get(id) ?? [];\n\t}\n\n\t/** `id` (optionally) and its ancestors, nearest first. */\n\tchain(id: string, self = true): CanvasNode[] {\n\t\tconst out: CanvasNode[] = [];\n\t\tconst seen = new Set<string>();\n\t\tlet at = this.get(id);\n\t\tif (at && !self) {\n\t\t\tseen.add(at.id);\n\t\t\tat = this.get(at.parentId);\n\t\t}\n\t\twhile (at && !seen.has(at.id)) {\n\t\t\tseen.add(at.id);\n\t\t\tout.push(at);\n\t\t\tat = this.get(at.parentId);\n\t\t}\n\t\treturn out;\n\t}\n\n\t/** Whether `a` is a strict ancestor of `b`. */\n\tisAncestor(a: string, b: string): boolean {\n\t\treturn this.chain(b, false).some((n) => n.id === a);\n\t}\n\n\t/** `id` and its descendants, pre-order. */\n\tsubtree(id: string): string[] {\n\t\tconst out: string[] = [];\n\t\tconst seen = new Set<string>();\n\t\tconst stack = [id];\n\t\twhile (stack.length) {\n\t\t\tconst at = stack.pop()!;\n\t\t\tif (seen.has(at) || !this.byId.has(at)) continue;\n\t\t\tseen.add(at);\n\t\t\tout.push(at);\n\t\t\tconst kids = this.children(at);\n\t\t\tfor (let i = kids.length - 1; i >= 0; i--) stack.push(kids[i]);\n\t\t}\n\t\treturn out;\n\t}\n}\n\nconst owningComponentIn = (g: Graph, id: string) => g.chain(id).find(isCanvasComponent)?.id;\nconst owningInstanceIn = (g: Graph, id: string) => g.chain(id).find(isCanvasInstance)?.id;\nconst enclosingInstanceIn = (g: Graph, id: string) => g.chain(id, false).find(isCanvasInstance)?.id;\nfunction rootInstanceIn(g: Graph, id: string): string | undefined {\n\tconst list = g.chain(id).filter(isCanvasInstance);\n\treturn list[list.length - 1]?.id;\n}\n\nfunction variantSetIn(g: Graph, componentId: string): CanvasNode | undefined {\n\tconst comp = g.get(componentId);\n\tif (!isCanvasComponent(comp)) return undefined;\n\tconst parent = g.get(comp!.parentId);\n\treturn isCanvasComponentSet(parent) ? parent : undefined;\n}\n\n/** The variant property values of a component: `data.component.variant`, else parsed from its name. */\nexport function getVariantValues(node: CanvasNode | undefined | null): Record<string, string> {\n\tconst explicit = getComponentData(node)?.variant;\n\tif (isRecord(explicit) && Object.keys(explicit).length) return stringRecord(explicit);\n\tconst name = getLayerName(node);\n\treturn name.includes('=') ? parseVariantName(name) : {};\n}\n\n/** Parses a variant name `Size=Large, State=Hover` into its pairs. */\nexport function parseVariantName(name: string): Record<string, string> {\n\tconst out: Record<string, string> = {};\n\tfor (const part of name.split(',')) {\n\t\tconst eq = part.indexOf('=');\n\t\tif (eq < 0) continue;\n\t\tconst k = part.slice(0, eq).trim();\n\t\tif (k) out[k] = part.slice(eq + 1).trim();\n\t}\n\treturn out;\n}\n\n/** Formats variant pairs as a name, in `order` (other properties after, sorted). */\nexport function formatVariantName(pairs: Readonly<Record<string, string>>, order: readonly string[] = []): string {\n\tconst keys = order.filter((k) => k in pairs);\n\tkeys.push(...Object.keys(pairs).filter((k) => !order.includes(k)).sort());\n\treturn keys.map((k) => `${k}=${pairs[k]}`).join(', ');\n}\n\nfunction variantsIn(g: Graph, setId: string): CanvasNode[] {\n\treturn g.children(setId).map((id) => g.get(id)!).filter(isCanvasComponent);\n}\n\nfunction variantOptionsIn(g: Graph, setId: string): { name: string; values: string[] }[] {\n\tconst out: { name: string; values: string[] }[] = [];\n\tfor (const v of variantsIn(g, setId)) {\n\t\tfor (const [k, value] of Object.entries(getVariantValues(v))) {\n\t\t\tlet entry = out.find((o) => o.name === k);\n\t\t\tif (!entry) out.push((entry = { name: k, values: [] }));\n\t\t\tif (!entry.values.includes(value)) entry.values.push(value);\n\t\t}\n\t}\n\treturn out;\n}\n\n/** A set's default variant: the one nearest its top-left corner. */\nfunction defaultVariantIn(g: Graph, setId: string): string | undefined {\n\tlet best: { key: [number, number, number]; id: string } | undefined;\n\tfor (const v of variantsIn(g, setId)) {\n\t\tconst { x, y } = v.position;\n\t\tconst key: [number, number, number] = [x + y, y, x];\n\t\tif (!best || key[0] < best.key[0] || (key[0] === best.key[0] && (key[1] < best.key[1] || (key[1] === best.key[1] && key[2] < best.key[2])))) {\n\t\t\tbest = { key, id: v.id };\n\t\t}\n\t}\n\treturn best?.id;\n}\n\nfunction findVariantIn(g: Graph, setId: string, wanted: Readonly<Record<string, string>>): string | undefined {\n\tconst entries = Object.entries(wanted);\n\tlet best: { score: number; id: string } | undefined;\n\tfor (const v of variantsIn(g, setId)) {\n\t\tconst values = getVariantValues(v);\n\t\tconst score = entries.filter(([k, val]) => values[k] === val).length;\n\t\tif (score === entries.length) return v.id;\n\t\tif (!best || score > best.score) best = { score, id: v.id };\n\t}\n\treturn best?.id;\n}\n\n/** The main component a component or set id stands for (a set's default variant). */\nfunction mainOfIn(g: Graph, id: string): string | undefined {\n\tconst node = g.get(id);\n\tif (isCanvasComponent(node)) return id;\n\tif (isCanvasComponentSet(node)) return defaultVariantIn(g, id);\n\treturn undefined;\n}\n\nconst siblingVariants = (g: Graph, a: string | undefined, b: string | undefined) =>\n\t!!a && !!b && a !== b && !!variantSetIn(g, a) && variantSetIn(g, a) === variantSetIn(g, b);\n\nfunction propertyDefsIn(g: Graph, componentId: string): Record<string, CanvasComponentPropertyDef> {\n\tconst comp = g.get(componentId);\n\tif (!isCanvasComponent(comp)) return {};\n\tconst set = variantSetIn(g, componentId);\n\tconst owner = set ? dataOf(set)[COMPONENT_SET_DATA_KEY] : dataOf(comp)[COMPONENT_DATA_KEY];\n\treturn isRecord(owner?.properties) ? (owner.properties as Record<string, CanvasComponentPropertyDef>) : {};\n}\n\nfunction validValue(def: CanvasComponentPropertyDef, v: unknown): boolean {\n\treturn def.type === 'boolean' ? typeof v === 'boolean' : typeof v === 'string';\n}\n\nfunction effectivePropsIn(g: Graph, componentId: string, values: Readonly<Record<string, unknown>> | undefined): Record<string, Value> {\n\tconst out: Record<string, Value> = {};\n\tfor (const [k, def] of Object.entries(propertyDefsIn(g, componentId))) {\n\t\tconst v = values?.[k];\n\t\tout[k] = v !== undefined && validValue(def, v) ? (v as Value) : def.default;\n\t}\n\treturn out;\n}\n\n/** Components instantiated (transitively) inside component `componentId`. */\nfunction componentDepsIn(g: Graph, componentId: string): Set<string> {\n\tconst deps = new Set<string>();\n\tconst queue = [componentId];\n\twhile (queue.length) {\n\t\tconst c = queue.pop()!;\n\t\tfor (const id of g.subtree(c)) {\n\t\t\tconst target = getInstanceData(g.get(id))?.componentId;\n\t\t\tif (target && !deps.has(target)) {\n\t\t\t\tdeps.add(target);\n\t\t\t\tqueue.push(target);\n\t\t\t}\n\t\t}\n\t}\n\treturn deps;\n}\n\n/** Whether placing an instance of `componentId` inside `parentId` would nest a component in itself. */\nfunction createsCycleIn(g: Graph, componentId: string, parentId: string | undefined): boolean {\n\tif (!parentId) return false;\n\tconst hosts = g.chain(parentId).filter(isCanvasComponent).map((n) => n.id);\n\tif (hosts.length === 0) return false;\n\tconst deps = componentDepsIn(g, componentId);\n\tdeps.add(componentId);\n\treturn hosts.some((h) => deps.has(h));\n}\n\n/** Whether an instance of `componentId` placed in `parentId` would contain itself. */\nexport function wouldCreateComponentCycle(nodes: readonly CanvasNode[], componentId: string, parentId: string | undefined): boolean {\n\tconst g = new Graph(nodes);\n\treturn createsCycleIn(g, mainOfIn(g, componentId) ?? componentId, parentId);\n}\n\n// ---------------------------------------------------------------------------------------------\n// Public queries\n\n/** The component a node belongs to (itself, or its nearest component ancestor). */\nexport function getOwningComponent(nodes: readonly CanvasNode[], id: string): string | undefined {\n\treturn owningComponentIn(new Graph(nodes), id);\n}\n\n/** The innermost instance containing `id` (`id` itself when it is an instance). */\nexport function getOwningInstance(nodes: readonly CanvasNode[], id: string): string | undefined {\n\treturn owningInstanceIn(new Graph(nodes), id);\n}\n\n/** The outermost instance containing `id`. */\nexport function getRootInstance(nodes: readonly CanvasNode[], id: string): string | undefined {\n\treturn rootInstanceIn(new Graph(nodes), id);\n}\n\n/** Whether `id` is a layer inside an instance (not a top-level instance itself). */\nexport function isInsideInstance(nodes: readonly CanvasNode[], id: string): boolean {\n\treturn enclosingInstanceIn(new Graph(nodes), id) !== undefined;\n}\n\n/** Top-level and nested instances of `componentId`. */\nexport function getComponentInstances(nodes: readonly CanvasNode[], componentId: string): string[] {\n\treturn nodes.filter((n) => getInstanceData(n)?.componentId === componentId).map((n) => n.id);\n}\n\n/** `linked`, or `missing-main` when the instance's main component is gone. */\nexport function getInstanceStatus(nodes: readonly CanvasNode[], id: string): 'linked' | 'missing-main' | undefined {\n\tconst g = new Graph(nodes);\n\tconst inst = getInstanceData(g.get(id));\n\tif (!inst) return undefined;\n\treturn isCanvasComponent(g.get(inst.componentId)) ? 'linked' : 'missing-main';\n}\n\n/** The variant set of a component (its parent set), if any. */\nexport function getVariantSet(nodes: readonly CanvasNode[], componentId: string): string | undefined {\n\treturn variantSetIn(new Graph(nodes), componentId)?.id;\n}\n\n/** Variant properties of a set with their values, in first-seen order. */\nexport function getVariantOptions(nodes: readonly CanvasNode[], setId: string): { name: string; values: string[] }[] {\n\treturn variantOptionsIn(new Graph(nodes), setId);\n}\n\n/** The variant of `setId` matching `wanted` (all values, or else the most). */\nexport function findVariant(nodes: readonly CanvasNode[], setId: string, wanted: Readonly<Record<string, string>>): string | undefined {\n\treturn findVariantIn(new Graph(nodes), setId, wanted);\n}\n\n/** A set's default variant: the one in its top-left corner. */\nexport function getDefaultVariant(nodes: readonly CanvasNode[], setId: string): string | undefined {\n\treturn defaultVariantIn(new Graph(nodes), setId);\n}\n\n/** Variant combinations shared by two or more variants of a set (conflicts to resolve). */\nexport function getVariantConflicts(nodes: readonly CanvasNode[], setId: string): string[][] {\n\tconst by = new Map<string, string[]>();\n\tfor (const v of variantsIn(new Graph(nodes), setId)) {\n\t\tconst key = formatVariantName(getVariantValues(v));\n\t\tby.set(key, [...(by.get(key) ?? []), v.id]);\n\t}\n\treturn [...by.values()].filter((ids) => ids.length > 1);\n}\n\n/** Component property definitions of a component (its set's for variants). */\nexport function getComponentPropertyDefs(nodes: readonly CanvasNode[], componentId: string): Record<string, CanvasComponentPropertyDef> {\n\treturn { ...propertyDefsIn(new Graph(nodes), componentId) };\n}\n\n/** An instance's property values, with defaults for those it doesn't set. */\nexport function getInstanceProperties(nodes: readonly CanvasNode[], id: string): Record<string, Value> {\n\tconst g = new Graph(nodes);\n\tconst inst = getInstanceData(g.get(id));\n\treturn inst ? effectivePropsIn(g, inst.componentId, inst.properties) : {};\n}\n\nexport interface CanvasComponentInfo {\n\tid: string;\n\tname: string;\n\tsetId?: string;\n\tvariant?: Record<string, string>;\n\t/** Instances linked to it (top-level and nested). */\n\tinstanceCount: number;\n}\n\nexport interface CanvasComponentSetInfo {\n\tid: string;\n\tname: string;\n\tvariants: CanvasComponentInfo[];\n\toptions: { name: string; values: string[] }[];\n\tdefaultVariantId?: string;\n}\n\n/** Components (lone ones and variants) and component sets, in document order. */\nexport function listCanvasComponents(nodes: readonly CanvasNode[]): { components: CanvasComponentInfo[]; sets: CanvasComponentSetInfo[] } {\n\tconst g = new Graph(nodes);\n\tconst counts = new Map<string, number>();\n\tfor (const n of nodes) {\n\t\tconst c = getInstanceData(n)?.componentId;\n\t\tif (c) counts.set(c, (counts.get(c) ?? 0) + 1);\n\t}\n\tconst components: CanvasComponentInfo[] = [];\n\tfor (const n of nodes) {\n\t\tif (!isCanvasComponent(n)) continue;\n\t\tconst set = variantSetIn(g, n.id);\n\t\tcomponents.push({\n\t\t\tid: n.id,\n\t\t\tname: getLayerName(n) || n.id,\n\t\t\t...(set ? { setId: set.id, variant: getVariantValues(n) } : {}),\n\t\t\tinstanceCount: counts.get(n.id) ?? 0\n\t\t});\n\t}\n\tconst sets = nodes.filter(isCanvasComponentSet).map((s) => ({\n\t\tid: s.id,\n\t\tname: getLayerName(s) || s.id,\n\t\tvariants: components.filter((c) => c.setId === s.id),\n\t\toptions: variantOptionsIn(g, s.id),\n\t\tdefaultVariantId: defaultVariantIn(g, s.id)\n\t}));\n\treturn { components, sets };\n}\n\nexport interface CanvasInstancePropertyInfo {\n\tname: string;\n\ttype: CanvasComponentPropertyType;\n\tvalue: Value;\n\tdefault: Value;\n\t/** Instance swap: preferred components (all components when empty). */\n\tpreferred?: string[];\n}\n\nexport interface CanvasInstanceInfo {\n\t/** The instance (the node itself, or the instance owning the inspected layer). */\n\tid: string;\n\t/** The outermost instance (where reset / push / detach act). */\n\trootId: string;\n\tnested: boolean;\n\tcomponentId: string;\n\tcomponentName?: string;\n\tstatus: 'linked' | 'missing-main';\n\tsetId?: string;\n\tvariant: Record<string, string>;\n\tvariantOptions: { name: string; values: string[] }[];\n\tproperties: CanvasInstancePropertyInfo[];\n\t/** Layers of the instance with overrides (the instance itself included). */\n\toverrides: { id: string; name: string; groups: string[] }[];\n}\n\n/** Everything an instance inspector shows, for an instance or a layer inside one. */\nexport function inspectCanvasInstance(nodes: readonly CanvasNode[], id: string): CanvasInstanceInfo | undefined {\n\tconst g = new Graph(nodes);\n\tconst instId = owningInstanceIn(g, id);\n\tif (!instId) return undefined;\n\tconst inst = g.get(instId)!;\n\tconst data = getInstanceData(inst)!;\n\tconst comp = g.get(data.componentId);\n\tconst linked = isCanvasComponent(comp);\n\tconst set = linked ? variantSetIn(g, data.componentId) : undefined;\n\tconst defs = propertyDefsIn(g, data.componentId);\n\tconst values = effectivePropsIn(g, data.componentId, data.properties);\n\tconst overrides: CanvasInstanceInfo['overrides'] = [];\n\tfor (const d of g.subtree(instId)) {\n\t\tconst groups = getNodeOverrides(g.get(d));\n\t\tif (groups.length) overrides.push({ id: d, name: getLayerName(g.get(d)) || d, groups: [...groups] });\n\t}\n\treturn {\n\t\tid: instId,\n\t\trootId: rootInstanceIn(g, instId) ?? instId,\n\t\tnested: enclosingInstanceIn(g, instId) !== undefined,\n\t\tcomponentId: data.componentId,\n\t\t...(linked ? { componentName: getLayerName(comp) || comp!.id } : {}),\n\t\tstatus: linked ? 'linked' : 'missing-main',\n\t\t...(set ? { setId: set.id } : {}),\n\t\tvariant: linked && set ? getVariantValues(comp) : {},\n\t\tvariantOptions: set ? variantOptionsIn(g, set.id) : [],\n\t\tproperties: Object.entries(defs).map(([name, def]) => ({\n\t\t\tname,\n\t\t\ttype: def.type,\n\t\t\tvalue: values[name],\n\t\t\tdefault: def.default,\n\t\t\t...(def.type === 'instanceSwap' ? { preferred: [...(def.preferred ?? [])] } : {})\n\t\t})),\n\t\toverrides\n\t};\n}\n\n// ---------------------------------------------------------------------------------------------\n// Instance rebuild\n\ninterface ResolvedSync {\n\tidGenerator: (instanceId: string, sourceId: string) => string;\n\ttextTypes: readonly string[];\n\tlayout: false | AutoLayoutOptions;\n\ttokens?: CanvasTokenOptions & { state?: TokenState };\n}\n\nfunction resolveSync(options: CanvasComponentSyncOptions = {}): ResolvedSync {\n\treturn {\n\t\tidGenerator: options.idGenerator ?? ((inst, src) => `${inst}:${src}`),\n\t\ttextTypes: options.textTypes ?? ['text'],\n\t\tlayout: options.layout === false ? false : options.layout === true || options.layout === undefined ? {} : options.layout,\n\t\ttokens: options.tokens\n\t};\n}\n\nconst stripLook = (data: Data): Data => {\n\tconst out = { ...data };\n\tfor (const k of LINK_KEYS) delete out[k];\n\treturn out;\n};\n\nfunction clearRuntime(node: Data, old: CanvasNode | undefined) {\n\tfor (const k of RUNTIME_KEYS) {\n\t\tif (old && (old as Data)[k] !== undefined) node[k] = (old as Data)[k];\n\t\telse delete node[k];\n\t}\n}\n\n/**\n * The value groups a node's token bindings write: values from variables are never overrides\n * (a different binding is, as `token.<key>`).\n */\nfunction tokenDrivenGroups(n: CanvasNode, options: CanvasTokenOptions | undefined): string[] {\n\tconst mine = stringRecord(dataOf(n).tokenBindings);\n\tconst out: string[] = [];\n\tfor (const key of Object.keys(mine)) {\n\t\tconst property = getCanvasTokenProperty(key, options);\n\t\tif (!property || !property.appliesTo(n)) continue;\n\t\tconst current = property.read(n);\n\t\tconst probe =\n\t\t\ttypeof current === 'number' ? current + 1 : typeof current === 'boolean' ? !current : `${current ?? ''}\\u0001`;\n\t\tfor (const g of diffOverrideGroups(n, property.write(n, probe as never))) if (!out.includes(g)) out.push(g);\n\t}\n\treturn out;\n}\n\n/** Groups a main layer's component property bindings set (values from instances, not overrides). */\nfunction propertyDrivenGroups(g: Graph, src: CanvasNode): string[] {\n\tconst out: string[] = [];\n\tlet at: CanvasNode | undefined = src;\n\tfor (let i = 0; at && i <= MAX_DEPTH; i++) {\n\t\tfor (const field of Object.keys(getComponentPropertyRefs(at))) out.push(fieldGroup(field));\n\t\tat = g.get(getComponentSourceId(at));\n\t}\n\treturn out;\n}\n\n/** What an instance root is compared with: its main, named after its set for variants. */\nfunction rootReference(g: Graph, comp: CanvasNode): CanvasNode {\n\tconst set = variantSetIn(g, comp.id);\n\tif (!set) return comp;\n\tconst label = dataOf(set).label;\n\treturn 'label' in dataOf(comp) && typeof label === 'string' ? withData(comp, { ...dataOf(comp), label }) : comp;\n}\n\n/** `measured` follows an explicit size (xyflow re-measures; geometry helpers read it first). */\nfunction fixMeasured(node: CanvasNode, old: CanvasNode | undefined): CanvasNode {\n\tconst measured = old?.measured ?? node.measured;\n\tconst size = sizeOf(node);\n\tconst w = size.width ?? size.styleWidth;\n\tconst h = size.height ?? size.styleHeight;\n\tif (!measured) {\n\t\tif (node.measured === undefined) return node;\n\t\tconst next = { ...node };\n\t\tdelete next.measured;\n\t\treturn next;\n\t}\n\tconst fixed = { ...measured, ...(w !== undefined ? { width: w } : {}), ...(h !== undefined ? { height: h } : {}) };\n\treturn same(fixed, node.measured) ? node : { ...node, measured: fixed };\n}\n\nfunction applyProp(g: Graph, node: CanvasNode, field: string, value: Value): CanvasNode {\n\tif (field === 'visible') return typeof value === 'boolean' ? writeOverrideGroup(node, 'hidden', !value) : node;\n\tif (field === 'component') {\n\t\tif (typeof value !== 'string' || !isCanvasInstance(node)) return node;\n\t\tconst main = mainOfIn(g, value);\n\t\treturn main ? writeOverrideGroup(node, 'component', main) : node;\n\t}\n\tif (field.startsWith('data.')) return writeOverrideGroup(node, field, typeof value === 'string' ? value : String(value));\n\treturn node;\n}\n\ninterface BuiltInstance {\n\t/** The rebuilt instance: its root, its layers (pre-order) and kept foreign content. */\n\tblock: CanvasNode[];\n\t/** Ids the block replaces. */\n\tremoved: Set<string>;\n}\n\n/**\n * Rebuilds the outermost instance `instId` from its main component (read from `g`), keeping\n * layer ids, overrides, property values and its own placement. `taken` holds every id in use\n * (new ids are added). Returns `undefined` when the main is missing (the instance stays as it is).\n */\nfunction buildInstance(g: Graph, instId: string, opts: ResolvedSync, taken: Set<string>): BuiltInstance | undefined {\n\tconst inst = g.get(instId);\n\tconst instData = getInstanceData(inst);\n\tif (!inst || !instData) return undefined;\n\tconst compId = instData.componentId;\n\tconst comp = g.get(compId);\n\tif (!comp || !isCanvasComponent(comp) || compId === instId || g.isAncestor(instId, compId) || g.isAncestor(compId, instId)) return undefined;\n\n\t// Old layers (kept for their ids and overrides) and foreign content (kept where it was).\n\tconst oldIds: string[] = [];\n\tconst foreign: string[] = [];\n\tconst stack = [...g.children(instId)];\n\twhile (stack.length) {\n\t\tconst id = stack.pop()!;\n\t\tif (getComponentSourceId(g.get(id)) !== undefined) {\n\t\t\toldIds.push(id);\n\t\t\tstack.push(...g.children(id));\n\t\t} else foreign.push(...g.subtree(id));\n\t}\n\tconst old = new Map(oldIds.map((id) => [id, g.get(id)!]));\n\tconst byParentSource = new Map<string, string>();\n\tconst bySource = new Map<string, string | null>();\n\tfor (const id of oldIds) {\n\t\tconst n = old.get(id)!;\n\t\tconst s = getComponentSourceId(n)!;\n\t\tconst key = `${n.parentId}\\u0000${s}`;\n\t\tif (!byParentSource.has(key)) byParentSource.set(key, id);\n\t\tbySource.set(s, bySource.has(s) ? null : id);\n\t}\n\tlet byPath: Map<string, string> | undefined;\n\tconst oldPaths = () => {\n\t\tconst out = new Map<string, string>();\n\t\tconst walk = (parent: string, prefix: string) => {\n\t\t\tconst counts = new Map<string, number>();\n\t\t\tfor (const c of g.children(parent)) {\n\t\t\t\tconst n = g.get(c)!;\n\t\t\t\tconst src = getComponentSourceId(n);\n\t\t\t\tif (src === undefined) continue;\n\t\t\t\tconst name = getLayerName(g.get(src) ?? n);\n\t\t\t\tconst k = counts.get(name) ?? 0;\n\t\t\t\tcounts.set(name, k + 1);\n\t\t\t\tconst path = `${prefix}/${k === 0 ? name : `${name}#${k}`}`;\n\t\t\t\tif (!out.has(path)) out.set(path, c);\n\t\t\t\twalk(c, path);\n\t\t\t}\n\t\t};\n\t\twalk(instId, '');\n\t\treturn out;\n\t};\n\n\tconst used = new Set<string>();\n\tconst fresh = (sourceId: string) => {\n\t\tconst base = opts.idGenerator(instId, sourceId);\n\t\tlet id = base;\n\t\tfor (let k = 2; taken.has(id); k++) id = `${base}~${k}`;\n\t\ttaken.add(id);\n\t\treturn id;\n\t};\n\tconst instProps = new Map<string, Record<string, Value>>();\n\tconst out: CanvasNode[] = [];\n\tconst isText = (n: CanvasNode) => typeof n.type === 'string' && opts.textTypes.includes(n.type);\n\n\t/** Copy of main layer `s` (with `o`'s overrides when matched). */\n\tconst copyLayer = (s: CanvasNode, o: CanvasNode | undefined, id: string, parentId: string): CanvasNode => {\n\t\tconst next = { ...s, id, parentId } as Data;\n\t\tconst data = stripLook(dataOf(s));\n\t\tif (isCanvasInstance(s)) data[INSTANCE_DATA_KEY] = { ...dataOf(s)[INSTANCE_DATA_KEY] };\n\t\tdata[COMPONENT_SOURCE_DATA_KEY] = s.id;\n\t\tnext.data = data;\n\t\tclearRuntime(next, o);\n\t\tlet n = next as CanvasNode;\n\t\tif (o) {\n\t\t\tlet groups = [...getNodeOverrides(o)];\n\t\t\tconst from = g.get(getComponentSourceId(o));\n\t\t\tif (from && from.id !== s.id && groups.length && !isText(o) && siblingVariants(g, owningComponentIn(g, from.id), owningComponentIn(g, s.id))) {\n\t\t\t\t// Switched variants: keep overrides of properties both variants' layers agreed on.\n\t\t\t\tconst differs = new Set(diffOverrideGroups(from, s));\n\t\t\t\tgroups = groups.filter((gr) => !differs.has(gr));\n\t\t\t}\n\t\t\tn = copyGroups(o, n, groups);\n\t\t\tn = keepLaidOut(s, o, n, groups);\n\t\t\tn = setOverrides(n, groups);\n\t\t}\n\t\treturn fixMeasured(n, o);\n\t};\n\n\t/** Where auto layout places / sizes a copy, keep the old copy's values (layout re-flows). */\n\tconst keepLaidOut = (s: CanvasNode, o: CanvasNode, n: CanvasNode, groups: readonly string[]): CanvasNode => {\n\t\tif (o.parentId !== n.parentId || groups.includes('size') || groups.includes('position')) return n;\n\t\tconst flows = !getChildLayout(n).absolute && !!getAutoLayout(g.get(s.parentId));\n\t\tlet next = n;\n\t\tif (flows) next = writeOverrideGroup(next, 'position', o.position);\n\t\tconst derived = (axis: 'width' | 'height') => {\n\t\t\tconst sizing = getLayoutSizing(next, axis);\n\t\t\treturn (sizing === 'hug' && !!getAutoLayout(next)) || (sizing === 'fill' && flows);\n\t\t};\n\t\tconst size = { ...sizeOf(next) };\n\t\tconst oldSize = sizeOf(o);\n\t\tif (derived('width')) {\n\t\t\tsize.width = oldSize.width;\n\t\t\tsize.styleWidth = oldSize.styleWidth;\n\t\t}\n\t\tif (derived('height')) {\n\t\t\tsize.height = oldSize.height;\n\t\t\tsize.styleHeight = oldSize.styleHeight;\n\t\t}\n\t\treturn writeOverrideGroup(next, 'size', size);\n\t};\n\n\t/**\n\t * An instance root looking like `look`, placed like `place`, with `old`'s overrides. A\n\t * top-level instance keeps its own placement, visibility, lock and modes; a swapped nested\n\t * instance is placed like the main layer it stands in for.\n\t */\n\tconst instanceRoot = (\n\t\tlook: CanvasNode,\n\t\tplace: CanvasNode,\n\t\told: CanvasNode | undefined,\n\t\tid: string,\n\t\tparentId: string | undefined,\n\t\tsourceId: string,\n\t\tinstance: CanvasInstanceData,\n\t\tpreviousComponent: string | undefined,\n\t\ttopLevel: boolean\n\t): CanvasNode => {\n\t\tconst reference = rootReference(g, look);\n\t\tconst next = { ...reference, id, position: { ...place.position } } as Data;\n\t\tif (parentId === undefined) delete next.parentId;\n\t\telse next.parentId = parentId;\n\t\tconst data = stripLook(dataOf(reference));\n\t\tdelete data.tokenModes;\n\t\tdata[INSTANCE_DATA_KEY] = { ...instance };\n\t\tdata[COMPONENT_SOURCE_DATA_KEY] = sourceId;\n\t\tnext.data = data;\n\t\tclearRuntime(next, old);\n\t\tlet n = next as CanvasNode;\n\t\tconst own = new Set<string>();\n\t\tlistGroups(place, own);\n\t\tlistGroups(n, own);\n\t\tn = copyGroups(place, n, [...own].filter((gr) => isOwnRootGroup(gr) && gr !== 'component' && gr !== 'props'));\n\t\tlet groups = old ? getNodeOverrides(old).filter((gr) => !topLevel || !isOwnRootGroup(gr)) : [];\n\t\tif (old && previousComponent && siblingVariants(g, previousComponent, look.id)) {\n\t\t\t// Switched variants: keep overrides where both variants agreed.\n\t\t\tconst differs = new Set(diffOverrideGroups(g.get(previousComponent)!, look));\n\t\t\tgroups = groups.filter((gr) => gr === 'data.label' || gr === 'data.name' || !differs.has(gr));\n\t\t}\n\t\t// The instance data (main and property values) was resolved by the caller.\n\t\tif (old) n = copyGroups(old, n, groups.filter((gr) => gr !== 'component' && gr !== 'props'));\n\t\treturn fixMeasured(setOverrides(n, groups), old);\n\t};\n\n\tconst applyChainProps = (n: CanvasNode, s: CanvasNode, enc: readonly string[]): CanvasNode => {\n\t\tconst chain: CanvasNode[] = [s];\n\t\twhile (chain.length < enc.length) {\n\t\t\tconst next = g.get(getComponentSourceId(chain[chain.length - 1]));\n\t\t\tif (!next) break;\n\t\t\tchain.push(next);\n\t\t}\n\t\tlet next = n;\n\t\tfor (let j = chain.length - 1; j >= 0; j--) {\n\t\t\tconst props = instProps.get(enc[j]);\n\t\t\tif (!props) continue;\n\t\t\tfor (const [field, prop] of Object.entries(getComponentPropertyRefs(chain[j]))) {\n\t\t\t\tconst group = fieldGroup(field);\n\t\t\t\tif (getNodeOverrides(next).includes(group) || (j > 0 && getNodeOverrides(s).includes(group))) continue;\n\t\t\t\tif (prop in props) next = applyProp(g, next, field, props[prop]);\n\t\t\t}\n\t\t}\n\t\treturn next;\n\t};\n\n\tconst copyChildren = (srcParent: string, newParent: string, prefix: string, enc: string[], expanding: string[], depth: number) => {\n\t\tconst counts = new Map<string, number>();\n\t\tfor (const sid of g.children(srcParent)) {\n\t\t\tconst s = g.get(sid)!;\n\t\t\tconst name = getLayerName(s);\n\t\t\tconst k = counts.get(name) ?? 0;\n\t\t\tcounts.set(name, k + 1);\n\t\t\tconst path = `${prefix}/${k === 0 ? name : `${name}#${k}`}`;\n\t\t\tlet oid = byParentSource.get(`${newParent}\\u0000${sid}`);\n\t\t\tif (oid !== undefined && used.has(oid)) oid = undefined;\n\t\t\tif (oid === undefined) {\n\t\t\t\tconst u = bySource.get(sid);\n\t\t\t\tif (u && !used.has(u)) oid = u;\n\t\t\t}\n\t\t\tif (oid === undefined) {\n\t\t\t\tbyPath ??= oldPaths();\n\t\t\t\tconst p = byPath.get(path);\n\t\t\t\tif (p && !used.has(p)) oid = p;\n\t\t\t}\n\t\t\tif (oid !== undefined) used.add(oid);\n\t\t\tconst o = oid !== undefined ? old.get(oid) : undefined;\n\t\t\tconst id = oid ?? fresh(sid);\n\t\t\tlet n = applyChainProps(copyLayer(s, o, id, newParent), s, enc);\n\t\t\tconst nested = getInstanceData(n);\n\t\t\tif (nested && depth < MAX_DEPTH) {\n\t\t\t\tconst target = nested.componentId;\n\t\t\t\tinstProps.set(id, effectivePropsIn(g, target, nested.properties));\n\t\t\t\tif (getInstanceData(s)?.componentId !== target) {\n\t\t\t\t\tconst main = g.get(target);\n\t\t\t\t\tif (isCanvasComponent(main) && !expanding.includes(target) && !g.isAncestor(target, instId)) {\n\t\t\t\t\t\t// A swapped nested instance shows its own main.\n\t\t\t\t\t\tconst previousComponent = o ? getInstanceData(o)?.componentId : undefined;\n\t\t\t\t\t\tn = applyChainProps(instanceRoot(main!, s, o, id, newParent, sid, nested, previousComponent, false), s, enc);\n\t\t\t\t\t\tout.push(n);\n\t\t\t\t\t\tcopyChildren(target, id, path, [id], [...expanding, target], depth + 1);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tout.push(n);\n\t\t\t\tcopyChildren(sid, id, path, [...enc, id], expanding, depth);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tout.push(n);\n\t\t\tcopyChildren(sid, id, path, enc, expanding, depth);\n\t\t}\n\t};\n\n\tconst root = instanceRoot(comp, inst, inst, instId, inst.parentId, compId, instData, getComponentSourceId(inst), true);\n\tinstProps.set(instId, effectivePropsIn(g, compId, instData.properties));\n\tcopyChildren(compId, instId, '', [instId], [compId], 0);\n\n\t// Foreign content stays where its parent still is.\n\tconst placed = new Set<string>([instId, ...out.map((n) => n.id)]);\n\tconst keptForeign: CanvasNode[] = [];\n\tfor (const id of foreign) {\n\t\tconst n = g.get(id)!;\n\t\tif (n.parentId && placed.has(n.parentId)) {\n\t\t\tplaced.add(id);\n\t\t\tkeptForeign.push(n);\n\t\t}\n\t}\n\tconst removed = new Set<string>([instId, ...oldIds, ...foreign]);\n\tlet block: CanvasNode[] = [root, ...out, ...keptForeign];\n\n\t// A resized instance: its layers follow their constraints (geometry overrides win).\n\tconst compSize = getNodeSize(comp);\n\tconst rootSize = getNodeSize(root);\n\tif (!getAutoLayout(root) && (Math.abs(compSize.width - rootSize.width) > 1e-9 || Math.abs(compSize.height - rootSize.height) > 1e-9)) {\n\t\tconst fixed = new Set(out.filter((n) => getNodeOverrides(n).some((gr) => gr === 'size' || gr === 'position')).map((n) => n.id));\n\t\tconst updates = applyConstraints(block, instId, compSize, opts.layout || {}).filter((u: CanvasNodeUpdate) => !fixed.has(u.id));\n\t\tblock = applyNodeUpdates(block, updates);\n\t}\n\t// Auto layout inside the instance (its ancestors are laid out once per batch).\n\tif (opts.layout) {\n\t\tconst frames = block.filter((n) => getAutoLayout(n)).map((n) => n.id);\n\t\tif (frames.length) block = applyNodeUpdates(block, layoutUpMany(block, frames, opts.layout));\n\t}\n\t// Unchanged layers keep their identity (so a no-op rebuild returns the same array).\n\tblock = block.map((n) => {\n\t\tconst prev = g.get(n.id);\n\t\treturn prev && prev !== n && same(prev, n) ? prev : n;\n\t});\n\treturn { block, removed };\n}\n\n/**\n * Rebuilds the outermost instances containing `ids` against one snapshot of `nodes` (they must\n * not depend on each other), then lays out the auto layout frames around them.\n */\nfunction rebuildBatch(nodes: CanvasNode[], ids: Iterable<string>, opts: ResolvedSync): CanvasNode[] {\n\tconst g = new Graph(nodes);\n\tconst taken = new Set(g.byId.keys());\n\tconst built = new Map<string, BuiltInstance>();\n\tfor (const id of ids) {\n\t\tconst root = g.get(id) ? rootInstanceIn(g, id) ?? id : undefined;\n\t\tif (!root || built.has(root)) continue;\n\t\tconst b = buildInstance(g, root, opts, taken);\n\t\tif (b) built.set(root, b);\n\t}\n\tif (built.size === 0) return nodes;\n\tconst removed = new Set<string>();\n\tfor (const b of built.values()) for (const id of b.removed) removed.add(id);\n\tlet result: CanvasNode[] = [];\n\tfor (const n of nodes) {\n\t\tconst b = built.get(n.id);\n\t\tif (b) result.push(...b.block);\n\t\telse if (!removed.has(n.id)) result.push(n);\n\t}\n\tif (opts.layout) {\n\t\tconst lookup = new Map(result.map((n) => [n.id, n]));\n\t\tconst inFrames = [...built.keys()].filter((r) => {\n\t\t\tconst parent = lookup.get(lookup.get(r)?.parentId ?? '');\n\t\t\treturn !!getAutoLayout(parent);\n\t\t});\n\t\tif (inFrames.length) result = applyNodeUpdates(result, layoutUpMany(result, inFrames, opts.layout));\n\t}\n\treturn result.length === nodes.length && result.every((n, i) => n === nodes[i]) ? nodes : result;\n}\n\n/**\n * Rebuilds every outermost instance of `comps` (and of the components holding them, outwards),\n * inner components first, plus `resync` roots.\n */\nfunction rebuildInstances(nodes: CanvasNode[], comps: Set<string>, resync: Set<string>, opts: ResolvedSync): CanvasNode[] {\n\tlet current = nodes;\n\tif (resync.size) {\n\t\tcurrent = rebuildBatch(current, resync, opts);\n\t\tconst after = new Graph(current);\n\t\tfor (const r of resync) {\n\t\t\tconst c = after.get(r) ? owningComponentIn(after, r) : undefined;\n\t\t\tif (c) comps.add(c);\n\t\t}\n\t}\n\tif (comps.size === 0) return current;\n\tconst g = new Graph(current);\n\tconst rootsOf = new Map<string, Set<string>>();\n\tfor (const n of current) {\n\t\tconst inst = getInstanceData(n);\n\t\tif (!inst) continue;\n\t\tconst root = rootInstanceIn(g, n.id) ?? n.id;\n\t\tlet set = rootsOf.get(inst.componentId);\n\t\tif (!set) rootsOf.set(inst.componentId, (set = new Set()));\n\t\tset.add(root);\n\t}\n\tconst level = new Map<string, number>([...comps].map((c) => [c, 0]));\n\tconst outer = new Map<string, string | undefined>();\n\tlet frontier = [...comps];\n\tfor (let rounds = 0; frontier.length && rounds < 64; rounds++) {\n\t\tconst next: string[] = [];\n\t\tfor (const c of frontier) {\n\t\t\tconst lc = level.get(c) ?? 0;\n\t\t\tfor (const r of rootsOf.get(c) ?? []) {\n\t\t\t\tif (!outer.has(r)) outer.set(r, owningComponentIn(g, r));\n\t\t\t\tconst x = outer.get(r);\n\t\t\t\tif (x && x !== c && (level.get(x) ?? -1) < lc + 1) {\n\t\t\t\t\tlevel.set(x, lc + 1);\n\t\t\t\t\tnext.push(x);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfrontier = next;\n\t}\n\t// Instances inside components by level (each level reads only lower ones), then the rest.\n\tconst batches = new Map<number, string[]>();\n\tfor (const [r, o] of outer) {\n\t\tconst l = o !== undefined ? level.get(o) ?? Number.MAX_SAFE_INTEGER : Number.MAX_SAFE_INTEGER;\n\t\tbatches.set(l, [...(batches.get(l) ?? []), r]);\n\t}\n\tfor (const l of [...batches.keys()].sort((a, b) => a - b)) current = rebuildBatch(current, batches.get(l)!, opts);\n\treturn current;\n}\n\nfunction finish<TNode extends CanvasNode>(original: readonly TNode[], result: CanvasNode[], opts: ResolvedSync): TNode[] {\n\tlet out = result;\n\tif (opts.tokens?.state) out = resolveCanvasTokens(out, opts.tokens.state, opts.tokens);\n\tif (out.length === original.length && out.every((n, i) => n === original[i])) return original as TNode[];\n\treturn out as TNode[];\n}\n\n/**\n * Rebuilds one instance from its main (keeping ids, overrides and property values).\n */\nexport function syncCanvasInstance<TNode extends CanvasNode>(nodes: readonly TNode[], instanceId: string, options: CanvasComponentSyncOptions = {}): TNode[] {\n\tconst opts = resolveSync(options);\n\treturn finish(nodes, rebuildInstances([...nodes], new Set(), new Set([instanceId]), opts), opts);\n}\n\n/**\n * Rebuilds every instance from its main, inner components first (e.g. after loading a\n * document whose instances were saved without their layers).\n */\nexport function syncAllCanvasInstances<TNode extends CanvasNode>(nodes: readonly TNode[], options: CanvasComponentSyncOptions = {}): TNode[] {\n\tconst opts = resolveSync(options);\n\tconst comps = new Set(nodes.filter(isCanvasComponent).map((n) => n.id));\n\treturn finish(nodes, rebuildInstances([...nodes], comps, new Set(), opts), opts);\n}\n\n/** Re-derives the sources and overrides of instance `instId` against its own main by comparing values. */\nfunction rebaseInstance(nodes: CanvasNode[], instId: string, tokens: CanvasTokenOptions | undefined): CanvasNode[] {\n\tconst g = new Graph(nodes);\n\tconst inst = getInstanceData(g.get(instId));\n\tif (!inst) return nodes;\n\tconst compId = inst.componentId;\n\tconst comp = g.get(compId);\n\tconst theirs = new Map<string, string>();\n\tconst mine = new Map<string, string>();\n\tconst paths = (root: string, viaSource: boolean, into: Map<string, string>, flip: boolean) => {\n\t\tconst walk = (parent: string, prefix: string) => {\n\t\t\tconst counts = new Map<string, number>();\n\t\t\tfor (const c of g.children(parent)) {\n\t\t\t\tconst n = g.get(c)!;\n\t\t\t\tconst name = getLayerName((viaSource && g.get(getComponentSourceId(n))) || n);\n\t\t\t\tconst k = counts.get(name) ?? 0;\n\t\t\t\tcounts.set(name, k + 1);\n\t\t\t\tconst path = `${prefix}/${k === 0 ? name : `${name}#${k}`}`;\n\t\t\t\tif (flip) {\n\t\t\t\t\tif (!into.has(path)) into.set(path, c);\n\t\t\t\t} else into.set(c, path);\n\t\t\t\twalk(c, path);\n\t\t\t}\n\t\t};\n\t\twalk(root, '');\n\t};\n\tif (comp) paths(compId, false, theirs, true);\n\tpaths(instId, true, mine, false);\n\tconst updates = new Map<string, CanvasNode>();\n\tfor (const [id, path] of mine) {\n\t\tconst n = g.get(id)!;\n\t\tlet main: string | undefined;\n\t\tlet cur = getComponentSourceId(n);\n\t\tfor (let i = 0; cur && i < MAX_DEPTH; i++) {\n\t\t\tif (cur !== compId && g.get(cur) && g.isAncestor(compId, cur)) {\n\t\t\t\tmain = cur;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcur = getComponentSourceId(g.get(cur));\n\t\t}\n\t\tmain ??= comp ? theirs.get(path) : undefined;\n\t\tconst m = g.get(main);\n\t\tlet next = n;\n\t\tif (m) {\n\t\t\tconst bound = new Set([...propertyDrivenGroups(g, m), ...tokenDrivenGroups(n, tokens)]);\n\t\t\tconst groups = new Set(getNodeOverrides(n).filter((gr) => gr !== 'component' && gr !== 'props'));\n\t\t\tfor (const gr of diffOverrideGroups(m, n)) if (gr !== 'size' && gr !== 'position' && !bound.has(gr)) groups.add(gr);\n\t\t\tif (isCanvasInstance(m) && isCanvasInstance(n) && getInstanceData(m)!.componentId !== getInstanceData(n)!.componentId) groups.add('component');\n\t\t\tnext = setOverrides(withData(n, { ...dataOf(n), [COMPONENT_SOURCE_DATA_KEY]: m.id }), groups);\n\t\t} else next = unlinked(n);\n\t\tif (next !== n) updates.set(id, next);\n\t}\n\tconst root = g.get(instId)!;\n\tlet nextRoot = withData(root, { ...dataOf(root), [COMPONENT_SOURCE_DATA_KEY]: compId });\n\tif (comp) {\n\t\tconst ref = rootReference(g, comp);\n\t\tconst driven = new Set(tokenDrivenGroups(root, tokens));\n\t\tconst groups = new Set(getNodeOverrides(root).filter((gr) => !isOwnRootGroup(gr)));\n\t\tfor (const gr of diffOverrideGroups(ref, root)) if (!isOwnRootGroup(gr) && !driven.has(gr)) groups.add(gr);\n\t\tnextRoot = setOverrides(nextRoot, groups);\n\t}\n\tupdates.set(instId, nextRoot);\n\treturn nodes.map((n) => updates.get(n.id) ?? n);\n}\n\n/** Whether auto layout decides a node's size (it hugs its content, or fills its auto layout parent). */\nfunction sizeFromLayout(g: Graph, node: CanvasNode): boolean {\n\tconst parentFlows = !!getAutoLayout(g.get(node.parentId)) && !getChildLayout(node).absolute;\n\treturn (['width', 'height'] as const).some((axis) => {\n\t\tconst sizing = getLayoutSizing(node, axis);\n\t\treturn (sizing === 'hug' && !!getAutoLayout(node)) || (sizing === 'fill' && parentFlows);\n\t});\n}\n\n/**\n * Keeps components and instances consistent after an edit from `before` to `after`:\n *\n * 1. Layers deleted from inside an instance come back hidden; nodes added inside an instance\n * move out next to it; pasted / duplicated copies of instance layers stop mirroring the main,\n * and a copied nested instance becomes an instance of its own main.\n * 2. Edits to instance layers are recorded as overrides (a value equal to the main's clears\n * the override; values from the same variable or from a component property are not\n * overrides; layer geometry inside an instance is not overridable, the instance's size is).\n * 3. Instances of components whose layers changed are rebuilt (nested components first),\n * as are instances whose main, properties or overrides were switched or reset.\n *\n * Returns `after` unchanged when there is nothing to do, so it can run on every change.\n */\nexport function syncCanvasComponents<TNode extends CanvasNode>(\n\tbefore: readonly TNode[],\n\tafter: readonly TNode[],\n\toptions: CanvasComponentSyncOptions = {}\n): TNode[] {\n\tif (before === after) return after as TNode[];\n\tconst opts = resolveSync(options);\n\tconst prev = new Graph(before);\n\tlet nodes: CanvasNode[] = [...after];\n\tconst resync = new Set<string>();\n\n\t// 1a. Layers deleted inside a surviving instance come back, hidden.\n\tconst afterIds = new Set(after.map((n) => n.id));\n\tconst restore: CanvasNode[] = [];\n\tfor (const b of before) {\n\t\tif (afterIds.has(b.id) || getComponentSourceId(b) === undefined || isCanvasInstance(b) && !enclosingInstanceIn(prev, b.id)) continue;\n\t\tconst root = rootInstanceIn(prev, b.id);\n\t\tif (!root || root === b.id || !afterIds.has(root)) continue;\n\t\trestore.push(b);\n\t}\n\tif (restore.length) {\n\t\tconst restoring = new Set(restore.map((n) => n.id));\n\t\tfor (const b of restore) nodes.push(b.parentId && restoring.has(b.parentId) ? b : writeOverrideGroup(b, 'hidden', true));\n\t\tfor (const b of restore) {\n\t\t\tconst r = rootInstanceIn(prev, b.id);\n\t\t\tif (r) resync.add(r);\n\t\t}\n\t}\n\n\t// 1b. Nodes added inside an existing instance move out; copies brought in elsewhere relink.\n\tlet g = new Graph(nodes);\n\tconst added = nodes.filter((n) => !prev.byId.has(n.id));\n\tif (added.length) {\n\t\tconst addedIds = new Set(added.map((n) => n.id));\n\t\tconst moves = new Map<string, CanvasNode>();\n\t\tfor (const n of added) {\n\t\t\tif (!n.parentId || addedIds.has(n.parentId)) continue;\n\t\t\tconst r = rootInstanceIn(g, n.parentId);\n\t\t\tif (!r || addedIds.has(r)) continue;\n\t\t\tconst rootNode = g.get(r)!;\n\t\t\tconst abs = getAbsolutePosition(n, g.byId);\n\t\t\tconst parent = g.get(rootNode.parentId);\n\t\t\tconst base = parent ? getAbsolutePosition(parent, g.byId) : { x: 0, y: 0 };\n\t\t\tconst moved = { ...n, position: { x: abs.x - base.x, y: abs.y - base.y } } as Data;\n\t\t\tif (rootNode.parentId) moved.parentId = rootNode.parentId;\n\t\t\telse delete moved.parentId;\n\t\t\tmoves.set(n.id, moved as CanvasNode);\n\t\t}\n\t\tif (moves.size) {\n\t\t\tnodes = nodes.map((n) => moves.get(n.id) ?? n);\n\t\t\tg = new Graph(nodes);\n\t\t}\n\t\tconst rebase: string[] = [];\n\t\tconst strips = new Map<string, CanvasNode>();\n\t\tfor (const n of added) {\n\t\t\tconst cur = g.get(n.id)!;\n\t\t\tif (isCanvasInstance(cur)) {\n\t\t\t\tif (enclosingInstanceIn(g, n.id) === undefined && getComponentSourceId(cur) !== getInstanceData(cur)!.componentId) rebase.push(n.id);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (getComponentSourceId(cur) === undefined && getNodeOverrides(cur).length === 0) continue;\n\t\t\tconst r = rootInstanceIn(g, n.id);\n\t\t\tif (!r || !addedIds.has(r) || moves.has(n.id)) strips.set(n.id, unlinked(cur, true));\n\t\t}\n\t\tif (strips.size) nodes = nodes.map((n) => strips.get(n.id) ?? n);\n\t\tfor (const id of rebase) nodes = rebaseInstance(nodes, id, opts.tokens);\n\t\tg = new Graph(nodes);\n\t}\n\n\t// 2. Overrides of edited instance layers.\n\tconst overrideUpdates = new Map<string, CanvasNode>();\n\tfor (const n of nodes) {\n\t\tconst b = prev.get(n.id);\n\t\tif (!b || b === n) continue;\n\t\tconst inst = owningInstanceIn(g, n.id);\n\t\tif (!inst || (getComponentSourceId(n) === undefined && inst !== n.id)) continue;\n\t\tconst topRoot = inst === n.id && enclosingInstanceIn(g, n.id) === undefined;\n\t\tconst bi = getInstanceData(b);\n\t\tconst ni = getInstanceData(n);\n\t\tconst raw = diffOverrideGroups(b, n);\n\t\tif (topRoot) {\n\t\t\tif (bi && ni && (bi.componentId !== ni.componentId || !same(bi.properties ?? {}, ni.properties ?? {}))) resync.add(n.id);\n\t\t\tif (raw.includes('size')) resync.add(n.id);\n\t\t}\n\t\tlet groups = raw.filter((gr) => gr !== 'position' && (gr !== 'size' || (topRoot && !sizeFromLayout(g, n))));\n\t\tif (topRoot) groups = groups.filter((gr) => !isOwnRootGroup(gr));\n\t\tlet cleared: string[] = [];\n\t\tconst srcId = topRoot ? ni?.componentId : getComponentSourceId(n);\n\t\tconst src = g.get(srcId);\n\t\tif (src) {\n\t\t\tconst reference = topRoot ? rootReference(g, src) : src;\n\t\t\tconst differs = new Set(diffOverrideGroups(reference, n));\n\t\t\tconst driven = new Set(tokenDrivenGroups(n, opts.tokens));\n\t\t\tgroups = groups.filter((gr) => differs.has(gr) && !driven.has(gr));\n\t\t\tcleared = raw.filter((gr) => gr !== 'size' && gr !== 'position' && !differs.has(gr) && getNodeOverrides(n).includes(gr));\n\t\t\tif (topRoot) cleared = cleared.filter((gr) => !isOwnRootGroup(gr));\n\t\t}\n\t\tconst lost = getNodeOverrides(b).some((gr) => !getNodeOverrides(n).includes(gr));\n\t\tconst nestedSwitch = !topRoot && raw.some((gr) => gr === 'component' || gr === 'props');\n\t\tif (groups.length || cleared.length) {\n\t\t\tconst set = new Set(getNodeOverrides(n));\n\t\t\tfor (const gr of groups) set.add(gr);\n\t\t\tfor (const gr of cleared) set.delete(gr);\n\t\t\tconst updated = setOverrides(n, set);\n\t\t\tif (updated !== n) overrideUpdates.set(n.id, updated);\n\t\t}\n\t\tif (lost || nestedSwitch) {\n\t\t\tconst r = rootInstanceIn(g, n.id);\n\t\t\tif (r) resync.add(r);\n\t\t}\n\t}\n\tif (overrideUpdates.size) {\n\t\tnodes = nodes.map((n) => overrideUpdates.get(n.id) ?? n);\n\t\tg = new Graph(nodes);\n\t}\n\n\t// 3. Components whose layers changed.\n\tconst comps = new Set<string>();\n\tfor (const n of nodes) {\n\t\tconst b = prev.get(n.id);\n\t\tif (b === n) continue;\n\t\tconst c = owningComponentIn(g, n.id);\n\t\tif (isCanvasComponentSet(n) && (!b || !same(dataOf(b)[COMPONENT_SET_DATA_KEY], dataOf(n)[COMPONENT_SET_DATA_KEY]))) {\n\t\t\tfor (const v of variantsIn(g, n.id)) comps.add(v.id);\n\t\t}\n\t\tif (!c) continue;\n\t\tif (!b) {\n\t\t\tcomps.add(c);\n\t\t\tcontinue;\n\t\t}\n\t\tconst structural =\n\t\t\tb.parentId !== n.parentId ||\n\t\t\t!same(dataOf(b)[COMPONENT_DATA_KEY], dataOf(n)[COMPONENT_DATA_KEY]) ||\n\t\t\t!same(getComponentPropertyRefs(b), getComponentPropertyRefs(n)) ||\n\t\t\tgetComponentSourceId(b) !== getComponentSourceId(n) ||\n\t\t\t!same(getNodeOverrides(b), getNodeOverrides(n));\n\t\tconst diff = diffOverrideGroups(b, n);\n\t\tif (!structural && (diff.length === 0 || (n.id === c && diff.every(isOwnRootGroup)))) continue;\n\t\tcomps.add(c);\n\t}\n\tfor (const b of before) {\n\t\tif (afterIds.has(b.id)) continue;\n\t\tconst c = owningComponentIn(prev, b.id);\n\t\tif (c && c !== b.id && g.get(c)) comps.add(c);\n\t}\n\t// Order changes among a component's layers (array order is layer order).\n\t{\n\t\tconst order = (list: readonly CanvasNode[]) => {\n\t\t\tconst m = new Map<string, string[]>();\n\t\t\tfor (const n of list) if (n.parentId) m.set(n.parentId, [...(m.get(n.parentId) ?? []), n.id]);\n\t\t\treturn m;\n\t\t};\n\t\tconst ob = order(before);\n\t\tconst oa = order(nodes);\n\t\tfor (const [parent, kids] of oa) {\n\t\t\tif (same(ob.get(parent), kids)) continue;\n\t\t\tconst c = owningComponentIn(g, parent);\n\t\t\tif (c && !owningInstanceIn(g, parent)) comps.add(c);\n\t\t}\n\t}\n\n\tnodes = rebuildInstances(nodes, comps, resync, opts);\n\treturn finish(after, nodes, opts);\n}\n\n// ---------------------------------------------------------------------------------------------\n// Operations\n\nfunction fail<TNode extends CanvasNode>(nodes: readonly TNode[], code: CanvasComponentErrorCode, reason: string): CanvasComponentResult<TNode> {\n\treturn { ok: false, nodes: nodes as TNode[], code, reason };\n}\n\nfunction done<TNode extends CanvasNode>(\n\tbefore: readonly TNode[],\n\tafter: CanvasNode[],\n\tids: string[],\n\toptions: CanvasComponentSyncOptions\n): CanvasComponentResult<TNode> {\n\treturn { ok: true, nodes: syncCanvasComponents(before, after as TNode[], options), ids };\n}\n\nfunction uniqueId(base: string, taken: ReadonlySet<string> | Map<string, unknown>): string {\n\tlet id = base;\n\tfor (let k = 2; taken.has(id); k++) id = `${base}-${k}`;\n\treturn id;\n}\n\nfunction sizedStyle(like: CanvasNode['style'], width: number, height: number): CanvasNode['style'] {\n\treturn typeof like === 'string' ? `width: ${width}px; height: ${height}px` : { width, height };\n}\n\n/** Wraps `ids` (siblings) in a new frame node at their bounds (+ `padding`), returning the new nodes. */\nfunction wrapInFrame(nodes: CanvasNode[], ids: string[], frame: { id: string; type: string; data: Data; padding: number }): CanvasNode[] {\n\tconst g = new Graph(nodes);\n\tconst roots = ids.map((id) => g.get(id)!);\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tfor (const n of roots) {\n\t\tconst { width, height } = getNodeSize(n);\n\t\tminX = Math.min(minX, n.position.x);\n\t\tminY = Math.min(minY, n.position.y);\n\t\tmaxX = Math.max(maxX, n.position.x + width);\n\t\tmaxY = Math.max(maxY, n.position.y + height);\n\t}\n\tminX -= frame.padding;\n\tminY -= frame.padding;\n\tmaxX += frame.padding;\n\tmaxY += frame.padding;\n\tconst width = maxX - minX;\n\tconst height = maxY - minY;\n\tconst parentId = roots[0].parentId;\n\tconst wrapper: CanvasNode = {\n\t\tid: frame.id,\n\t\ttype: frame.type,\n\t\tposition: { x: minX, y: minY },\n\t\twidth,\n\t\theight,\n\t\tstyle: sizedStyle(roots[0].style, width, height),\n\t\tdata: frame.data,\n\t\t...(parentId ? { parentId } : {})\n\t};\n\tconst selected = new Set(ids);\n\tconst firstIndex = nodes.findIndex((n) => selected.has(n.id));\n\tconst out: CanvasNode[] = [];\n\tnodes.forEach((n, i) => {\n\t\tif (i === firstIndex) out.push(wrapper);\n\t\tout.push(selected.has(n.id) ? { ...n, parentId: frame.id, position: { x: n.position.x - minX, y: n.position.y - minY } } : n);\n\t});\n\treturn out;\n}\n\n/**\n * Create Component: a single frame-like node (one with children, a frame / group type or auto\n * layout) becomes a component; anything else is wrapped in a new component frame. Refuses\n * layers inside an instance and layers that don't share a parent.\n */\nexport function createCanvasComponent<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tids: Iterable<string>,\n\toptions: CreateCanvasComponentOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst picked = [...new Set(ids)].filter((id) => g.get(id));\n\tconst roots = picked.filter((id) => !picked.some((o) => o !== id && g.isAncestor(o, id)));\n\tif (roots.length === 0) return fail(nodes, 'not-found', 'Select the layers to make a component.');\n\tif (roots.some((id) => enclosingInstanceIn(g, id) !== undefined)) {\n\t\treturn fail(nodes, 'inside-instance', \"Layers inside an instance can't become components: detach the instance first.\");\n\t}\n\tif (roots.some((id) => isCanvasComponent(g.get(id)) || isCanvasComponentSet(g.get(id)))) {\n\t\treturn fail(nodes, 'invalid', 'The selection is already a component.');\n\t}\n\tif (new Set(roots.map((id) => g.get(id)!.parentId ?? '')).size > 1) return fail(nodes, 'invalid', 'Select layers that share a parent.');\n\tconst frameTypes = options.frameTypes ?? ['group', 'frame'];\n\tif (roots.length === 1) {\n\t\tconst node = g.get(roots[0])!;\n\t\tconst frameLike = !isCanvasInstance(node) && (g.children(node.id).length > 0 || (typeof node.type === 'string' && frameTypes.includes(node.type)) || !!getAutoLayout(node));\n\t\tif (frameLike) {\n\t\t\tconst after = nodes.map((n) => (n.id === node.id ? withData(n, { ...dataOf(n), [COMPONENT_DATA_KEY]: {} }) : n));\n\t\t\treturn done(nodes, after, [node.id], options);\n\t\t}\n\t}\n\tconst id = options.id ?? uniqueId('component', g.byId);\n\tconst name = options.name ?? (roots.length === 1 ? getLayerName(g.get(roots[0])) || 'Component' : 'Component');\n\tconst after = wrapInFrame([...nodes], roots, { id, type: options.frameType ?? 'group', data: { label: name, [COMPONENT_DATA_KEY]: {} }, padding: 0 });\n\treturn done(nodes, after, [id], options);\n}\n\n/**\n * Combine as Variants: wraps sibling components in a component set. Each variant takes its\n * `data.component.variant`, else the `Prop=Value` pairs of its name, else `Property 1=…`.\n */\nexport function combineCanvasComponentsAsVariants<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tids: Iterable<string>,\n\toptions: CanvasComponentSyncOptions & { id?: string; name?: string; frameType?: string; padding?: number } = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst picked = [...new Set(ids)].filter((id) => g.get(id));\n\tif (picked.length === 0) return fail(nodes, 'not-found', 'Select components to combine.');\n\tif (!picked.every((id) => isCanvasComponent(g.get(id)))) return fail(nodes, 'invalid', 'Select components only.');\n\tif (picked.some((id) => variantSetIn(g, id))) return fail(nodes, 'invalid', 'A component is already a variant of a set.');\n\tif (picked.some((id) => owningInstanceIn(g, id))) return fail(nodes, 'inside-instance', \"Components inside an instance can't be combined.\");\n\tif (new Set(picked.map((id) => g.get(id)!.parentId ?? '')).size > 1) return fail(nodes, 'invalid', 'Select components that share a parent.');\n\tconst ordered = nodes.filter((n) => picked.includes(n.id)).map((n) => n.id);\n\tconst first = g.get(ordered[0])!;\n\tconst firstName = getLayerName(first);\n\tconst name = options.name ?? (firstName && !firstName.includes('=') ? firstName.split('/')[0].trim() : 'Component');\n\tconst id = options.id ?? uniqueId('component-set', g.byId);\n\tlet after = wrapInFrame([...nodes], ordered, {\n\t\tid,\n\t\ttype: options.frameType ?? 'group',\n\t\tdata: { label: name, [COMPONENT_SET_DATA_KEY]: {} },\n\t\tpadding: options.padding ?? 20\n\t});\n\t// Property order: as first seen in the variants' names.\n\tconst order: string[] = [];\n\tfor (const vid of ordered) for (const k of Object.keys(getVariantValues(g.get(vid)))) if (!order.includes(k)) order.push(k);\n\tafter = after.map((n) => {\n\t\tconst k = ordered.indexOf(n.id);\n\t\tif (k < 0) return n;\n\t\tlet variant = getVariantValues(n);\n\t\tif (Object.keys(variant).length === 0) variant = { 'Property 1': k === 0 ? 'Default' : `Variant${k + 1}` };\n\t\tconst comp = { ...(getComponentData(n) ?? {}), variant };\n\t\treturn withData(n, { ...dataOf(n), [COMPONENT_DATA_KEY]: comp, label: formatVariantName(variant, order) });\n\t});\n\treturn done(nodes, after, [id], options);\n}\n\n/**\n * Create Instance of a component (or of a set's default variant). Refuses placing it inside\n * its own main (directly or through nested instances) or inside another instance.\n */\nexport function createCanvasInstance<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tcomponentId: string,\n\toptions: CreateCanvasInstanceOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst main = mainOfIn(g, componentId);\n\tif (!main) return fail(nodes, 'not-found', 'Choose a component or component set.');\n\tconst parentId = options.parentId;\n\tif (parentId !== undefined && !g.get(parentId)) return fail(nodes, 'not-found', `Unknown parent \"${parentId}\".`);\n\tif (createsCycleIn(g, main, parentId)) return fail(nodes, 'cycle', \"An instance can't be placed inside its own main component.\");\n\tif (parentId !== undefined && owningInstanceIn(g, parentId)) return fail(nodes, 'inside-instance', \"Instances can't gain layers: place it outside the instance.\");\n\tconst comp = g.get(main)!;\n\tconst id = options.id ?? uniqueId(`${main}-instance`, g.byId);\n\tif (g.get(id)) return fail(nodes, 'invalid', `Id \"${id}\" is taken.`);\n\tlet position = options.position;\n\tif (!position) {\n\t\tif (parentId !== undefined) position = { x: 0, y: 0 };\n\t\telse {\n\t\t\tconst top = g.chain(main).pop()!;\n\t\t\tposition = { x: getAbsolutePosition(top, g.byId).x + getNodeSize(top).width + 40, y: getAbsolutePosition(comp, g.byId).y };\n\t\t}\n\t}\n\tconst root: CanvasNode = {\n\t\tid,\n\t\ttype: comp.type,\n\t\tposition: { ...position },\n\t\tdata: { [INSTANCE_DATA_KEY]: { componentId: main, properties: effectivePropsIn(g, main, undefined) }, [COMPONENT_SOURCE_DATA_KEY]: main },\n\t\t...(parentId !== undefined ? { parentId } : {})\n\t};\n\tconst opts = resolveSync(options);\n\tlet after = rebuildBatch([...nodes, root], [id], opts);\n\tif (options.properties && Object.keys(options.properties).length) {\n\t\tconst set = setCanvasInstanceProperties(after, [id], options.properties, { ...options, tokens: undefined });\n\t\tif (!set.ok) return fail(nodes, set.code, set.reason);\n\t\tafter = set.nodes;\n\t}\n\treturn { ok: true, nodes: finish(nodes, after, opts), ids: [id] };\n}\n\n/**\n * Detach Instance: the instance becomes a plain frame with the same look; its layers stop\n * mirroring the main, and instances nested in it become instances of their own mains. Nested\n * instances can't be detached on their own.\n */\nexport function detachCanvasInstance<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tids: Iterable<string>,\n\toptions: CanvasComponentSyncOptions = {}\n): CanvasComponentResult<TNode> {\n\tlet current: CanvasNode[] = [...nodes];\n\tconst doneIds: string[] = [];\n\tfor (const id of new Set(ids)) {\n\t\tconst g = new Graph(current);\n\t\tif (!isCanvasInstance(g.get(id))) continue;\n\t\tif (enclosingInstanceIn(g, id) !== undefined) {\n\t\t\treturn fail(nodes, 'nested-instance', \"A nested instance can't be detached on its own: detach the outer instance first.\");\n\t\t}\n\t\tconst nested: string[] = [];\n\t\tconst updates = new Map<string, CanvasNode>();\n\t\tfor (const d of g.subtree(id)) {\n\t\t\tif (d === id || enclosingInstanceIn(g, d) !== id) continue;\n\t\t\tif (isCanvasInstance(g.get(d))) nested.push(d);\n\t\t\telse updates.set(d, unlinked(g.get(d)!));\n\t\t}\n\t\tconst root = g.get(id)!;\n\t\tconst data = { ...dataOf(root) };\n\t\tdelete data[INSTANCE_DATA_KEY];\n\t\tdelete data[COMPONENT_SOURCE_DATA_KEY];\n\t\tdelete data[COMPONENT_OVERRIDES_DATA_KEY];\n\t\tupdates.set(id, withData(root, data));\n\t\tcurrent = current.map((n) => updates.get(n.id) ?? n);\n\t\tfor (const nn of nested) current = rebaseInstance(current, nn, options.tokens);\n\t\tdoneIds.push(id);\n\t}\n\tif (doneIds.length === 0) return fail(nodes, 'invalid', 'Select an instance.');\n\treturn done(nodes, current, doneIds, options);\n}\n\n/**\n * Reset overrides: `group` resets one override group of layer `id`; without it, an instance\n * resets every override in it (its own size stays) and its property values, a layer resets all of\n * its own.\n */\nexport function resetCanvasInstanceOverrides<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tid: string,\n\tgroup?: string,\n\toptions: CanvasComponentSyncOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst node = g.get(id);\n\tconst inst = node ? owningInstanceIn(g, id) : undefined;\n\tif (!node || !inst) return fail(nodes, 'not-found', 'Select an instance or a layer inside one.');\n\tconst top = inst === id && enclosingInstanceIn(g, id) === undefined;\n\tconst updates = new Map<string, CanvasNode>();\n\tconst resetProps = (n: CanvasNode): CanvasNode => {\n\t\tconst data = getInstanceData(n)!;\n\t\tconst defaults = effectivePropsIn(g, data.componentId, undefined);\n\t\treturn same(data.properties ?? {}, defaults) ? n : withData(n, { ...dataOf(n), [INSTANCE_DATA_KEY]: { ...data, properties: defaults } });\n\t};\n\tif (group !== undefined) {\n\t\tlet next = setOverrides(node, getNodeOverrides(node).filter((gr) => gr !== group));\n\t\tif (top && group === 'props') next = resetProps(next);\n\t\tupdates.set(id, next);\n\t} else if (inst === id) {\n\t\tfor (const d of g.subtree(id)) {\n\t\t\tconst n = g.get(d)!;\n\t\t\tconst keep = d === id && top ? getNodeOverrides(n).filter((gr) => gr === 'size') : [];\n\t\t\tupdates.set(d, setOverrides(n, keep));\n\t\t}\n\t\tif (top) updates.set(id, resetProps(updates.get(id)!));\n\t} else updates.set(id, setOverrides(node, []));\n\tconst after = nodes.map((n) => updates.get(n.id) ?? n);\n\tconst changed = after.some((n, i) => n !== nodes[i]);\n\tif (!changed) return { ok: true, nodes: nodes as TNode[], ids: [] };\n\tconst synced = syncCanvasComponents(nodes, after as TNode[], options);\n\treturn { ok: true, nodes: synced, ids: [inst] };\n}\n\n/**\n * Push overrides to main: copies every overridden value of the (outermost) instance to the\n * main layers it mirrors (geometry, swaps and property values stay with the instance), clears\n * those overrides and updates every other instance.\n */\nexport function pushCanvasInstanceOverrides<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tid: string,\n\toptions: CanvasComponentSyncOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst inst = g.get(id) ? rootInstanceIn(g, id) : undefined;\n\tif (!inst) return fail(nodes, 'not-found', 'Select an instance.');\n\tconst compId = getInstanceData(g.get(inst))!.componentId;\n\tif (!isCanvasComponent(g.get(compId))) return fail(nodes, 'missing-main', 'The main component was deleted: restore it first.');\n\tconst updates = new Map<string, CanvasNode>();\n\tlet pushed = 0;\n\tfor (const d of g.subtree(inst)) {\n\t\tconst n = g.get(d)!;\n\t\tconst overrides = getNodeOverrides(n);\n\t\tif (overrides.length === 0) continue;\n\t\tconst src = d === inst ? compId : getComponentSourceId(n);\n\t\tif (!src || !g.get(src) || (src !== compId && !g.isAncestor(compId, src))) continue;\n\t\tconst groups = overrides.filter(\n\t\t\t(gr) => gr !== 'size' && gr !== 'position' && gr !== 'component' && gr !== 'props' && !(d === inst && (gr === 'data.label' || gr === 'data.name' || isOwnRootGroup(gr)))\n\t\t);\n\t\tif (groups.length === 0) continue;\n\t\tpushed++;\n\t\tupdates.set(src, copyGroups(n, updates.get(src) ?? g.get(src)!, groups));\n\t\tupdates.set(d, setOverrides(n, overrides.filter((gr) => !groups.includes(gr))));\n\t}\n\tif (pushed === 0) return fail(nodes, 'no-overrides', 'The instance has no changes to push.');\n\tconst after = nodes.map((n) => updates.get(n.id) ?? n);\n\treturn done(nodes, after, [inst], options);\n}\n\n/** Swaps instances to another component (or a set's default variant), keeping same-named property values. */\nexport function swapCanvasInstance<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tids: Iterable<string>,\n\tcomponentId: string,\n\toptions: CanvasComponentSyncOptions & { keepTextOverridesOnly?: boolean } = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst main = mainOfIn(g, componentId);\n\tif (!main) return fail(nodes, 'not-found', 'Choose a component or component set.');\n\tconst updates = new Map<string, CanvasNode>();\n\tconst list = [...new Set(ids)];\n\tfor (const id of list) {\n\t\tconst n = g.get(id);\n\t\tconst inst = getInstanceData(n);\n\t\tif (!n || !inst) return fail(nodes, 'invalid', `\"${id}\" is not an instance.`);\n\t\tif (g.isAncestor(main, id) || createsCycleIn(g, main, n.parentId)) return fail(nodes, 'cycle', \"An instance can't show the component it is inside.\");\n\t\tconst defaults = effectivePropsIn(g, main, undefined);\n\t\tconst properties: Record<string, Value> = {};\n\t\tfor (const [k, d] of Object.entries(defaults)) properties[k] = inst.properties?.[k] !== undefined && typeof inst.properties[k] === typeof d ? inst.properties[k] : d;\n\t\tupdates.set(id, withData(n, { ...dataOf(n), [INSTANCE_DATA_KEY]: { ...inst, componentId: main, properties } }));\n\t\tif (options.keepTextOverridesOnly) {\n\t\t\tfor (const d of g.subtree(id)) {\n\t\t\t\tconst layer = updates.get(d) ?? g.get(d)!;\n\t\t\t\tconst text = typeof layer.type === 'string' && (options.textTypes ?? ['text']).includes(layer.type);\n\t\t\t\tconst keep = getNodeOverrides(layer).filter((gr) => text || (d === id && gr === 'size'));\n\t\t\t\tupdates.set(d, setOverrides(layer, keep));\n\t\t\t}\n\t\t}\n\t}\n\tif (list.length === 0) return fail(nodes, 'invalid', 'Select an instance.');\n\tconst after = nodes.map((n) => updates.get(n.id) ?? n);\n\treturn done(nodes, after, list, options);\n}\n\n/**\n * Sets instance property values: variant properties switch to the matching variant of the set\n * (keeping compatible overrides), others must be defined on the component with the right type\n * (boolean, text, or a component id for instance swap).\n */\nexport function setCanvasInstanceProperties<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tids: Iterable<string>,\n\tproperties: Readonly<Record<string, unknown>>,\n\toptions: CanvasComponentSyncOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst updates = new Map<string, CanvasNode>();\n\tconst list = [...new Set(ids)];\n\tif (list.length === 0) return fail(nodes, 'invalid', 'Select an instance.');\n\tfor (const id of list) {\n\t\tconst n = g.get(id);\n\t\tconst inst = getInstanceData(n);\n\t\tif (!n || !inst) return fail(nodes, 'invalid', `\"${id}\" is not an instance.`);\n\t\tconst set = variantSetIn(g, inst.componentId);\n\t\tconst options_ = set ? variantOptionsIn(g, set.id) : [];\n\t\tconst wanted = { ...getVariantValues(g.get(inst.componentId)) };\n\t\tconst rest: [string, unknown][] = [];\n\t\tfor (const [k, v] of Object.entries(properties)) {\n\t\t\tconst option = options_.find((o) => o.name === k);\n\t\t\tif (option) {\n\t\t\t\tconst value = typeof v === 'string' ? v : String(v);\n\t\t\t\tif (!option.values.includes(value)) return fail(nodes, 'invalid', `\"${k}\" must be one of ${option.values.join(', ')}.`);\n\t\t\t\twanted[k] = value;\n\t\t\t} else rest.push([k, v]);\n\t\t}\n\t\tlet target = inst.componentId;\n\t\tif (set && !same(wanted, getVariantValues(g.get(inst.componentId)))) target = findVariantIn(g, set.id, wanted) ?? target;\n\t\tconst defs = propertyDefsIn(g, target);\n\t\tconst values: Record<string, Value> = { ...(inst.properties ?? {}) };\n\t\tfor (const [k, v] of rest) {\n\t\t\tconst def = defs[k];\n\t\t\tif (!def) {\n\t\t\t\tconst known = [...Object.keys(defs), ...options_.map((o) => o.name)];\n\t\t\t\treturn fail(nodes, 'unknown-property', `Unknown property \"${k}\"${known.length ? ` (known: ${known.join(', ')})` : ''}.`);\n\t\t\t}\n\t\t\tif (def.type === 'boolean') {\n\t\t\t\tconst b = typeof v === 'boolean' ? v : v === 'true' ? true : v === 'false' ? false : undefined;\n\t\t\t\tif (b === undefined) return fail(nodes, 'type-mismatch', `\"${k}\" takes true or false.`);\n\t\t\t\tvalues[k] = b;\n\t\t\t} else if (def.type === 'text') {\n\t\t\t\tif (typeof v !== 'string' && typeof v !== 'number') return fail(nodes, 'type-mismatch', `\"${k}\" takes text.`);\n\t\t\t\tvalues[k] = String(v);\n\t\t\t} else {\n\t\t\t\tconst main = typeof v === 'string' ? mainOfIn(g, v) : undefined;\n\t\t\t\tif (!main) return fail(nodes, 'type-mismatch', `\"${k}\" takes a component id.`);\n\t\t\t\tif (main === target || componentDepsIn(g, main).has(target) || createsCycleIn(g, main, n.parentId)) {\n\t\t\t\t\treturn fail(nodes, 'cycle', `\"${k}\" can't show a component that contains this one.`);\n\t\t\t\t}\n\t\t\t\tvalues[k] = main;\n\t\t\t}\n\t\t}\n\t\tconst properties2 = effectivePropsIn(g, target, values);\n\t\tupdates.set(id, withData(n, { ...dataOf(n), [INSTANCE_DATA_KEY]: { ...inst, componentId: target, properties: properties2 } }));\n\t}\n\tconst after = nodes.map((n) => updates.get(n.id) ?? n);\n\treturn done(nodes, after, list, options);\n}\n\n/**\n * Adds, edits (`def`) or deletes (`null`) a component property on a component (variants share\n * their set's). `newName` renames it, carrying layer bindings and instance values along.\n */\nexport function setCanvasComponentProperty<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tcomponentId: string,\n\tname: string,\n\tdef: CanvasComponentPropertyDef | null,\n\toptions: CanvasComponentSyncOptions & { newName?: string } = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst target = g.get(componentId);\n\tconst compId = isCanvasComponentSet(target) ? undefined : owningComponentIn(g, componentId);\n\tconst owner = isCanvasComponentSet(target) ? target! : compId ? variantSetIn(g, compId) ?? g.get(compId)! : undefined;\n\tif (!owner) return fail(nodes, 'not-found', 'Select a component or component set.');\n\tconst key = isCanvasComponentSet(owner) ? COMPONENT_SET_DATA_KEY : COMPONENT_DATA_KEY;\n\tconst ownerData = { ...(dataOf(owner)[key] ?? {}) };\n\tconst defs: Record<string, CanvasComponentPropertyDef> = { ...(ownerData.properties ?? {}) };\n\tconst finalName = (options.newName ?? name).trim();\n\tif (!finalName) return fail(nodes, 'invalid', 'Property names must not be empty.');\n\tif (isCanvasComponentSet(owner) && variantOptionsIn(g, owner.id).some((o) => o.name === finalName)) {\n\t\treturn fail(nodes, 'invalid', `\"${finalName}\" is a variant property.`);\n\t}\n\tif (finalName !== name && defs[finalName]) return fail(nodes, 'invalid', `\"${finalName}\" exists already.`);\n\tif (def === null) {\n\t\tif (!defs[name]) return fail(nodes, 'unknown-property', `Unknown property \"${name}\".`);\n\t\tdelete defs[name];\n\t} else {\n\t\tif (def.type === 'boolean' && typeof def.default !== 'boolean') return fail(nodes, 'type-mismatch', 'A boolean property needs a boolean default.');\n\t\tif (def.type === 'text' && typeof def.default !== 'string') return fail(nodes, 'type-mismatch', 'A text property needs a text default.');\n\t\tif (def.type === 'instanceSwap' && !mainOfIn(g, def.default)) return fail(nodes, 'type-mismatch', 'An instance swap property needs a component as default.');\n\t\tif (finalName !== name) delete defs[name];\n\t\tdefs[finalName] = def;\n\t}\n\townerData.properties = defs;\n\tconst comps = isCanvasComponentSet(owner) ? variantsIn(g, owner.id).map((v) => v.id) : [owner.id];\n\tconst updates = new Map<string, CanvasNode>();\n\tupdates.set(owner.id, withData(owner, { ...dataOf(owner), [key]: ownerData }));\n\tconst renamed = def === null ? null : finalName;\n\tif (renamed !== name) {\n\t\tfor (const c of comps) {\n\t\t\tfor (const d of g.subtree(c)) {\n\t\t\t\tconst n = updates.get(d) ?? g.get(d)!;\n\t\t\t\tconst refs = { ...getComponentPropertyRefs(n) };\n\t\t\t\tlet touched = false;\n\t\t\t\tfor (const [field, prop] of Object.entries(refs)) {\n\t\t\t\t\tif (prop !== name) continue;\n\t\t\t\t\ttouched = true;\n\t\t\t\t\tif (renamed === null) delete refs[field];\n\t\t\t\t\telse refs[field] = renamed;\n\t\t\t\t}\n\t\t\t\tif (!touched) continue;\n\t\t\t\tconst data: Data = { ...dataOf(n), [COMPONENT_PROPERTY_REFS_DATA_KEY]: refs };\n\t\t\t\tif (Object.keys(refs).length === 0) delete data[COMPONENT_PROPERTY_REFS_DATA_KEY];\n\t\t\t\tupdates.set(d, withData(n, data));\n\t\t\t}\n\t\t}\n\t\tfor (const n of nodes) {\n\t\t\tconst inst = getInstanceData(n);\n\t\t\tif (!inst || !comps.includes(inst.componentId) || !inst.properties || !(name in inst.properties)) continue;\n\t\t\tconst props = { ...inst.properties };\n\t\t\tconst v = props[name];\n\t\t\tdelete props[name];\n\t\t\tif (renamed !== null) props[renamed] = v;\n\t\t\tconst cur = updates.get(n.id) ?? n;\n\t\t\tupdates.set(n.id, withData(cur, { ...dataOf(cur), [INSTANCE_DATA_KEY]: { ...inst, properties: props } }));\n\t\t}\n\t}\n\tconst after = nodes.map((n) => updates.get(n.id) ?? n);\n\treturn done(nodes, after, [owner.id], options);\n}\n\n/**\n * Binds a field of a main-component layer to a component property (`null` unbinds): `visible`\n * takes a boolean property, `data.<key>` a text property, `component` (on a nested instance) an\n * instance swap property.\n */\nexport function bindCanvasComponentProperty<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tlayerId: string,\n\tfield: CanvasComponentPropertyField,\n\tname: string | null,\n\toptions: CanvasComponentSyncOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst layer = g.get(layerId);\n\tconst compId = layer ? owningComponentIn(g, layerId) : undefined;\n\tif (!layer || !compId || compId === layerId) return fail(nodes, 'not-found', 'Select a layer of a main component.');\n\tconst inst = enclosingInstanceIn(g, layerId);\n\tif (inst && g.isAncestor(compId, inst)) return fail(nodes, 'inside-instance', \"Layers of a nested instance can't be bound: bind the nested instance's own main.\");\n\tconst refs = { ...getComponentPropertyRefs(layer) };\n\tif (name === null) delete refs[field];\n\telse {\n\t\tconst def = propertyDefsIn(g, compId)[name];\n\t\tif (!def) return fail(nodes, 'unknown-property', `Unknown property \"${name}\".`);\n\t\tconst ok =\n\t\t\t(def.type === 'boolean' && field === 'visible') ||\n\t\t\t(def.type === 'text' && field.startsWith('data.')) ||\n\t\t\t(def.type === 'instanceSwap' && field === 'component' && isCanvasInstance(layer));\n\t\tif (!ok) return fail(nodes, 'type-mismatch', `A ${def.type} property can't drive \"${field}\".`);\n\t\trefs[field] = name;\n\t}\n\tconst data = { ...dataOf(layer) };\n\tif (Object.keys(refs).length) data[COMPONENT_PROPERTY_REFS_DATA_KEY] = refs;\n\telse delete data[COMPONENT_PROPERTY_REFS_DATA_KEY];\n\tconst after = nodes.map((n) => (n.id === layerId ? withData(n, data) : n));\n\treturn done(nodes, after, [layerId], options);\n}\n\n/**\n * Restore Main Component of an instance whose main was deleted: the instance becomes the main\n * again, with its old id, and its layers take the ids their copies mirror, so every other\n * instance re-links. Its property values become the property defaults.\n */\nexport function restoreCanvasMainComponent<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tid: string,\n\toptions: CanvasComponentSyncOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst inst = getInstanceData(g.get(id));\n\tif (!inst) return fail(nodes, 'invalid', 'Select an instance.');\n\tif (g.get(inst.componentId)) return fail(nodes, 'invalid', 'The main component still exists.');\n\tif (enclosingInstanceIn(g, id) !== undefined) return fail(nodes, 'nested-instance', 'Select the outer instance.');\n\tconst compId = inst.componentId;\n\tconst sub = g.subtree(id);\n\tconst map = new Map<string, string>([[id, compId]]);\n\tconst taken = new Set(nodes.map((n) => n.id));\n\ttaken.add(compId);\n\tfor (const d of sub) {\n\t\tif (d === id) continue;\n\t\tconst src = enclosingInstanceIn(g, d) === id ? getComponentSourceId(g.get(d)) : undefined;\n\t\tif (src && !taken.has(src)) {\n\t\t\tmap.set(d, src);\n\t\t\ttaken.add(src);\n\t\t}\n\t}\n\tconst nested: string[] = [];\n\tconst replaced = new Map<string, CanvasNode>();\n\tfor (const d of sub) {\n\t\tlet n = g.get(d)!;\n\t\tconst nid = map.get(d) ?? d;\n\t\tconst parentId = n.parentId !== undefined ? map.get(n.parentId) ?? n.parentId : undefined;\n\t\tn = { ...n, id: nid, ...(parentId !== undefined ? { parentId } : {}) };\n\t\tif (d === id) {\n\t\t\tconst defs: Record<string, CanvasComponentPropertyDef> = {};\n\t\t\tfor (const [k, v] of Object.entries(inst.properties ?? {})) {\n\t\t\t\tif (typeof v === 'boolean') defs[k] = { type: 'boolean', default: v };\n\t\t\t\telse if (typeof v === 'string' && isCanvasComponent(g.get(v))) defs[k] = { type: 'instanceSwap', default: v };\n\t\t\t\telse if (typeof v === 'string') defs[k] = { type: 'text', default: v };\n\t\t\t}\n\t\t\tconst data = { ...dataOf(n) };\n\t\t\tdelete data[INSTANCE_DATA_KEY];\n\t\t\tdelete data[COMPONENT_SOURCE_DATA_KEY];\n\t\t\tdelete data[COMPONENT_OVERRIDES_DATA_KEY];\n\t\t\tdata[COMPONENT_DATA_KEY] = Object.keys(defs).length ? { properties: defs } : {};\n\t\t\tn = withData(n, data);\n\t\t} else if (enclosingInstanceIn(g, d) === id) {\n\t\t\tif (isCanvasInstance(n)) nested.push(nid);\n\t\t\telse n = unlinked(n);\n\t\t}\n\t\treplaced.set(d, n);\n\t}\n\tlet after: CanvasNode[] = nodes.map((n) => replaced.get(n.id) ?? n);\n\tfor (const nn of nested) after = rebaseInstance(after, nn, options.tokens);\n\treturn done(nodes, after, [compId], options);\n}\n\n// ---------------------------------------------------------------------------------------------\n// Editing the variants of a component set\n\nexport interface AddCanvasVariantOptions extends CanvasComponentSyncOptions {\n\t/** Variant to duplicate (default: the set's default variant). */\n\tfrom?: string;\n\t/**\n\t * Property values of the new variant; properties not named keep the source's values. New\n\t * property names are added to every other variant with `defaultValue`. Default: the next\n\t * unused value of the first property (`Variant5`, …), see `getNextVariantValues`.\n\t */\n\tvariant?: Readonly<Record<string, string>>;\n\t/** Position in the set's space (default: right of the source, or by the set's auto layout). */\n\tposition?: XYPosition;\n\t/** Id of the new variant (its layers get `<id>-<sourceLayerId>`). */\n\tid?: string;\n\t/** Value the other variants take for a new property (default `Default`). */\n\tdefaultValue?: string;\n}\n\nexport interface SetCanvasVariantPropertiesOptions extends CanvasComponentSyncOptions {\n\t/** Value the other variants take for a new property (default `Default`). */\n\tdefaultValue?: string;\n}\n\n/** Gap between a new variant and its neighbours, and the set's padding around it. */\nconst VARIANT_GAP = 20;\n\nconst variantKey = (values: Readonly<Record<string, string>>) => formatVariantName(values);\n\nfunction variantNameProblem(kind: 'property' | 'value', s: string): string | undefined {\n\tconst label = kind === 'property' ? 'Variant property names' : 'Variant values';\n\tif (!s) return `${label} must not be empty.`;\n\tif (/[,=]/.test(s)) return `${label} can't contain \",\" or \"=\".`;\n\treturn undefined;\n}\n\n/** Trimmed, validated variant pairs, or the reason they are invalid. */\nfunction cleanVariantPairs(pairs: Readonly<Record<string, unknown>>): Record<string, string> | string {\n\tconst out: Record<string, string> = {};\n\tfor (const [rawKey, rawValue] of Object.entries(pairs)) {\n\t\tif (typeof rawValue !== 'string') return `\"${rawKey}\" takes a text value.`;\n\t\tconst k = rawKey.trim();\n\t\tconst v = rawValue.trim();\n\t\tconst problem = variantNameProblem('property', k) ?? variantNameProblem('value', v);\n\t\tif (problem) return problem;\n\t\tout[k] = v;\n\t}\n\treturn out;\n}\n\n/** Property order of a set (first seen, in layer order) for the given values. */\nfunction variantOrder(ids: readonly string[], values: ReadonlyMap<string, Readonly<Record<string, string>>>): string[] {\n\tconst order: string[] = [];\n\tfor (const id of ids) for (const k of Object.keys(values.get(id) ?? {})) if (!order.includes(k)) order.push(k);\n\treturn order;\n}\n\n/** `node` carrying variant `values` (explicit `data.component.variant`, and its name). */\nfunction withVariantValues<T extends CanvasNode>(node: T, values: Record<string, string>, order: readonly string[]): T {\n\tconst name = formatVariantName(values, order);\n\tconst data: Data = { ...dataOf(node), [COMPONENT_DATA_KEY]: { ...(getComponentData(node) ?? {}), variant: values }, label: name };\n\tif (typeof data.name === 'string' && data.name.includes('=')) data.name = name;\n\treturn same(dataOf(node), data) ? node : withData(node, data);\n}\n\n/**\n * The variant among `list` matching most of `wanted`; ties go to the one matching the earlier\n * properties, then to the first in layer order.\n */\nfunction closestVariant(list: readonly CanvasNode[], wanted: Readonly<Record<string, string>>): string | undefined {\n\tconst entries = Object.entries(wanted);\n\tlet best: { score: number[]; id: string } | undefined;\n\tconst better = (a: number[], b: number[]) => {\n\t\tfor (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return a[i] > b[i];\n\t\treturn false;\n\t};\n\tfor (const v of list) {\n\t\tconst values = getVariantValues(v);\n\t\tconst matches = entries.map(([k, val]) => (values[k] === val ? 1 : 0));\n\t\tconst score = [matches.reduce((a: number, b) => a + b, 0), ...matches];\n\t\tif (!best || better(score, best.score)) best = { score, id: v.id };\n\t}\n\treturn best?.id;\n}\n\ninterface VariantEdit {\n\tset: CanvasNode;\n\tvariants: CanvasNode[];\n\tvalues: Map<string, Record<string, string>>;\n}\n\nfunction variantEditIn(g: Graph, setId: string): VariantEdit | { code: CanvasComponentErrorCode; reason: string } {\n\tconst set = g.get(setId);\n\tif (!isCanvasComponentSet(set)) return { code: 'not-found', reason: 'Choose a component set.' };\n\tif (owningInstanceIn(g, setId)) return { code: 'inside-instance', reason: \"A component set inside an instance can't be edited: edit its main.\" };\n\tconst variants = variantsIn(g, setId);\n\treturn { set: set!, variants, values: new Map(variants.map((v) => [v.id, getVariantValues(v)])) };\n}\n\n/** Adds the properties of `pairs` no variant has yet to every variant, with `defaultValue`. */\nfunction addMissingProperties(edit: VariantEdit, pairs: Readonly<Record<string, string>>, defaultValue: string): string | undefined {\n\tconst known = new Set<string>();\n\tfor (const v of edit.values.values()) for (const k of Object.keys(v)) known.add(k);\n\tconst added = Object.keys(pairs).filter((k) => !known.has(k));\n\tif (added.length === 0) return undefined;\n\tconst problem = variantNameProblem('value', defaultValue);\n\tif (problem) return problem;\n\tconst defs = dataOf(edit.set)[COMPONENT_SET_DATA_KEY]?.properties;\n\tconst clash = isRecord(defs) ? added.find((k) => k in defs) : undefined;\n\tif (clash) return `\"${clash}\" is a component property.`;\n\tfor (const [id, v] of edit.values) {\n\t\tconst next = { ...v };\n\t\tfor (const k of added) next[k] = defaultValue;\n\t\tedit.values.set(id, next);\n\t}\n\treturn undefined;\n}\n\n/** The name of the first of `ids` whose combination another variant shares. */\nfunction duplicateAmong(values: ReadonlyMap<string, Readonly<Record<string, string>>>, ids: Iterable<string>): string | undefined {\n\tfor (const id of ids) {\n\t\tconst key = variantKey(values.get(id)!);\n\t\tfor (const [other, v] of values) {\n\t\t\tif (other !== id && variantKey(v) === key) return formatVariantName(values.get(id)!, variantOrder([...values.keys()], values));\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/** Variant nodes whose values changed in `edit`, by id. */\nfunction writeVariantEdit(edit: VariantEdit, extra: readonly string[] = []): Map<string, CanvasNode> {\n\tconst order = variantOrder([...edit.variants.map((v) => v.id), ...extra], edit.values);\n\tconst updates = new Map<string, CanvasNode>();\n\tfor (const v of edit.variants) {\n\t\tconst next = withVariantValues(v, edit.values.get(v.id)!, order);\n\t\tif (next !== v) updates.set(v.id, next);\n\t}\n\treturn updates;\n}\n\nfunction nextVariantValuesIn(g: Graph, setId: string, fromId: string | undefined): Record<string, string> {\n\tconst variants = variantsIn(g, setId);\n\tconst base = getVariantValues(g.get(fromId));\n\tconst first = variantOptionsIn(g, setId)[0];\n\tconst prop = first?.name ?? 'Property 1';\n\tconst used = new Set(variants.map((v) => variantKey(getVariantValues(v))));\n\tfor (let k = variants.length + 1; ; k++) {\n\t\tconst values = { ...base, [prop]: `Variant${k}` };\n\t\tif (!first?.values.includes(`Variant${k}`) && !used.has(variantKey(values))) return values;\n\t}\n}\n\n/**\n * The values `addCanvasVariant` gives a new variant by default: the source's (default: the\n * set's default variant) with the first property set to the next unused `Variant<n>`.\n */\nexport function getNextVariantValues(nodes: readonly CanvasNode[], setId: string, from?: string): Record<string, string> {\n\tconst g = new Graph(nodes);\n\treturn nextVariantValuesIn(g, setId, from ?? defaultVariantIn(g, setId));\n}\n\nfunction relayoutSet(nodes: CanvasNode[], setId: string, options: CanvasComponentSyncOptions): CanvasNode[] {\n\tif (options.layout === false || !getAutoLayout(nodes.find((n) => n.id === setId))) return nodes;\n\treturn applyNodeUpdates(nodes, layoutUpMany(nodes, [setId], typeof options.layout === 'object' ? options.layout : {}));\n}\n\n/**\n * Adds a variant to a component set by duplicating one (default: the default variant) with new\n * ids and the given property values. New property names are added to every other variant (with\n * `defaultValue`), so the set stays consistent; a combination another variant has is refused.\n * The copy goes right of its source (the set grows to fit) or where the set's auto layout puts it.\n */\nexport function addCanvasVariant<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tsetId: string,\n\toptions: AddCanvasVariantOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst edit = variantEditIn(g, setId);\n\tif (!('set' in edit)) return fail(nodes, edit.code, edit.reason);\n\tif (edit.variants.length === 0) return fail(nodes, 'invalid', 'The set has no variant to duplicate.');\n\tconst fromId = options.from ?? defaultVariantIn(g, setId)!;\n\tconst source = edit.variants.find((v) => v.id === fromId);\n\tif (!source) return fail(nodes, 'not-found', `\"${fromId}\" is not a variant of this set.`);\n\tconst wanted = cleanVariantPairs(options.variant ?? nextVariantValuesIn(g, setId, fromId));\n\tif (typeof wanted === 'string') return fail(nodes, 'invalid', wanted);\n\tconst problem = addMissingProperties(edit, wanted, (options.defaultValue ?? 'Default').trim());\n\tif (problem) return fail(nodes, 'invalid', problem);\n\tconst id = options.id ?? uniqueId(`${setId}-variant`, g.byId);\n\tif (g.get(id)) return fail(nodes, 'invalid', `Id \"${id}\" is taken.`);\n\tedit.values.set(id, { ...edit.values.get(fromId)!, ...wanted });\n\tconst dup = duplicateAmong(edit.values, [id]);\n\tif (dup) return fail(nodes, 'invalid', `A variant with ${dup} exists already.`);\n\n\t// Placement: the given position, the set's auto layout, or the first free spot right of the source.\n\tconst autoLayout = !!getAutoLayout(edit.set);\n\tconst { width, height } = getNodeSize(source);\n\tlet position: XYPosition = options.position ? { ...options.position } : { ...source.position };\n\tif (!options.position && !autoLayout) {\n\t\tconst rects = edit.variants.map((v) => ({ ...v.position, ...getNodeSize(v) }));\n\t\tposition = { x: source.position.x + width + VARIANT_GAP, y: source.position.y };\n\t\tfor (let k = 0; k <= rects.length; k++) {\n\t\t\tconst hit = rects.find((r) => position.x < r.x + r.width && r.x < position.x + width && position.y < r.y + r.height && r.y < position.y + height);\n\t\t\tif (!hit) break;\n\t\t\tposition = { x: hit.x + hit.width + VARIANT_GAP, y: position.y };\n\t\t}\n\t}\n\n\t// Copy the source's subtree with fresh ids, after the set's last layer.\n\tconst taken = new Set(g.byId.keys());\n\ttaken.add(id);\n\tconst map = new Map<string, string>([[fromId, id]]);\n\tconst sub = g.subtree(fromId);\n\tfor (const d of sub) {\n\t\tif (d === fromId) continue;\n\t\tconst nid = uniqueId(`${id}-${d}`, taken);\n\t\ttaken.add(nid);\n\t\tmap.set(d, nid);\n\t}\n\tconst order = variantOrder([...edit.variants.map((v) => v.id), id], edit.values);\n\tconst subSet = new Set(sub);\n\tconst copies: CanvasNode[] = nodes\n\t\t.filter((n) => subSet.has(n.id))\n\t\t.map((n) => {\n\t\t\tconst next = { ...n, id: map.get(n.id)! } as Data;\n\t\t\tif (n.parentId !== undefined && n.id !== fromId) next.parentId = map.get(n.parentId) ?? n.parentId;\n\t\t\tfor (const k of RUNTIME_KEYS) delete next[k];\n\t\t\tif (n.id !== fromId) return next as CanvasNode;\n\t\t\tnext.position = position;\n\t\t\treturn withVariantValues(next as CanvasNode, edit.values.get(id)!, order);\n\t\t});\n\tconst updates = writeVariantEdit(edit, [id]);\n\tconst inSet = new Set(g.subtree(setId));\n\tlet last = -1;\n\tnodes.forEach((n, i) => {\n\t\tif (inSet.has(n.id)) last = i;\n\t});\n\tlet after: CanvasNode[] = [];\n\tnodes.forEach((n, i) => {\n\t\tafter.push(updates.get(n.id) ?? n);\n\t\tif (i === last) after.push(...copies);\n\t});\n\n\tif (autoLayout && !options.position) after = relayoutSet(after, setId, options);\n\telse {\n\t\tconst size = getNodeSize(edit.set);\n\t\tconst w = Math.max(size.width, position.x + width + VARIANT_GAP);\n\t\tconst h = Math.max(size.height, position.y + height + VARIANT_GAP);\n\t\tif (w !== size.width || h !== size.height) after = applyNodeUpdates(after, [{ id: setId, width: w, height: h }]);\n\t}\n\treturn done(nodes, after, [id], options);\n}\n\n/**\n * Removes a variant from its set (refusing the last one). Its instances switch to the closest\n * remaining variant (most matching values), keeping their overrides like a variant switch, and\n * instance swap values and defaults pointing at it point at that variant too.\n */\nexport function removeCanvasVariant<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tvariantId: string,\n\toptions: CanvasComponentSyncOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst set = variantSetIn(g, variantId);\n\tif (!set) return fail(nodes, 'not-found', 'Choose a variant of a component set.');\n\tif (owningInstanceIn(g, set.id)) return fail(nodes, 'inside-instance', \"A component set inside an instance can't be edited: edit its main.\");\n\tconst others = variantsIn(g, set.id).filter((v) => v.id !== variantId);\n\tif (others.length === 0) return fail(nodes, 'invalid', 'A component set needs at least one variant: delete the set instead.');\n\tconst replacement = closestVariant(others, getVariantValues(g.get(variantId)))!;\n\tconst swap = <T>(v: T): T => (v === variantId ? (replacement as T) : v);\n\n\t// 1. Re-point instances (and instance swap values / defaults), synced while the variant exists.\n\tconst repointed = nodes.map((n) => {\n\t\tconst data = dataOf(n);\n\t\tlet next: Data | undefined;\n\t\tconst inst = getInstanceData(n);\n\t\tif (inst) {\n\t\t\tconst moved: CanvasInstanceData = { ...inst, componentId: swap(inst.componentId) };\n\t\t\tif (inst.properties) moved.properties = Object.fromEntries(Object.entries(inst.properties).map(([k, v]) => [k, swap(v)]));\n\t\t\tif (!same(moved, inst)) next = { ...data, [INSTANCE_DATA_KEY]: moved };\n\t\t}\n\t\tfor (const key of [COMPONENT_DATA_KEY, COMPONENT_SET_DATA_KEY]) {\n\t\t\tconst owner = data[key];\n\t\t\tif (!isRecord(owner) || !isRecord(owner.properties)) continue;\n\t\t\tconst defs: Record<string, CanvasComponentPropertyDef> = {};\n\t\t\tfor (const [k, def] of Object.entries(owner.properties as Record<string, CanvasComponentPropertyDef>)) {\n\t\t\t\tdefs[k] =\n\t\t\t\t\tdef.type === 'instanceSwap'\n\t\t\t\t\t\t? { ...def, default: swap(def.default), ...(def.preferred ? { preferred: [...new Set(def.preferred.map(swap))] } : {}) }\n\t\t\t\t\t\t: def;\n\t\t\t}\n\t\t\tif (!same(defs, owner.properties)) next = { ...(next ?? data), [key]: { ...owner, properties: defs } };\n\t\t}\n\t\treturn next ? withData(n, next) : n;\n\t});\n\tconst mid = syncCanvasComponents(nodes, repointed, options);\n\n\t// 2. Delete the variant (the set's auto layout closes the gap).\n\tconst gone = new Set(new Graph(mid).subtree(variantId));\n\tconst after = relayoutSet(\n\t\tmid.filter((n) => !gone.has(n.id)),\n\t\tset.id,\n\t\toptions\n\t);\n\treturn { ok: true, nodes: syncCanvasComponents(mid, after as TNode[], options), ids: [replacement] };\n}\n\n/**\n * Edits a variant's property values (merged into its current ones). New property names are\n * added to every other variant (with `defaultValue`); a combination another variant has is\n * refused. Instances of the variant stay on it and pick up the new values.\n */\nexport function setCanvasVariantProperties<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tvariantId: string,\n\tvalues: Readonly<Record<string, string>>,\n\toptions: SetCanvasVariantPropertiesOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst set = variantSetIn(g, variantId);\n\tif (!set) return fail(nodes, 'not-found', 'Choose a variant of a component set.');\n\tconst edit = variantEditIn(g, set.id);\n\tif (!('set' in edit)) return fail(nodes, edit.code, edit.reason);\n\tconst wanted = cleanVariantPairs(values);\n\tif (typeof wanted === 'string') return fail(nodes, 'invalid', wanted);\n\tconst problem = addMissingProperties(edit, wanted, (options.defaultValue ?? 'Default').trim());\n\tif (problem) return fail(nodes, 'invalid', problem);\n\tedit.values.set(variantId, { ...edit.values.get(variantId)!, ...wanted });\n\tconst dup = duplicateAmong(edit.values, [variantId]);\n\tif (dup) return fail(nodes, 'invalid', `A variant with ${dup} exists already.`);\n\tconst updates = writeVariantEdit(edit);\n\tif (updates.size === 0) return { ok: true, nodes: nodes as TNode[], ids: [variantId] };\n\treturn done(nodes, nodes.map((n) => updates.get(n.id) ?? n), [variantId], options);\n}\n\n/** Rewrites property values (via `fn`) stored on instances of the set's variants. */\nfunction updateInstanceValues(g: Graph, setId: string, updates: Map<string, CanvasNode>, fn: (props: Record<string, Value>) => Record<string, Value>) {\n\tconst ids = new Set(variantsIn(g, setId).map((v) => v.id));\n\tfor (const n of g.nodes) {\n\t\tconst inst = getInstanceData(n);\n\t\tif (!inst?.properties || !ids.has(inst.componentId)) continue;\n\t\tconst props = fn({ ...inst.properties });\n\t\tif (same(props, inst.properties)) continue;\n\t\tconst cur = updates.get(n.id) ?? n;\n\t\tupdates.set(n.id, withData(cur, { ...dataOf(cur), [INSTANCE_DATA_KEY]: { ...inst, properties: props } }));\n\t}\n}\n\n/** Renames a variant property across a set: every variant, and the values instances store. */\nexport function renameCanvasVariantProperty<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tsetId: string,\n\tname: string,\n\tnewName: string,\n\toptions: CanvasComponentSyncOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst edit = variantEditIn(g, setId);\n\tif (!('set' in edit)) return fail(nodes, edit.code, edit.reason);\n\tconst known = variantOptionsIn(g, setId);\n\tif (!known.some((o) => o.name === name)) return fail(nodes, 'unknown-property', `Unknown variant property \"${name}\".`);\n\tconst to = newName.trim();\n\tconst problem = variantNameProblem('property', to);\n\tif (problem) return fail(nodes, 'invalid', problem);\n\tif (to === name) return { ok: true, nodes: nodes as TNode[], ids: [setId] };\n\tif (known.some((o) => o.name === to)) return fail(nodes, 'invalid', `\"${to}\" exists already.`);\n\tif (to in propertyDefsIn(g, edit.variants[0].id)) return fail(nodes, 'invalid', `\"${to}\" is a component property.`);\n\tconst rename = <T>(v: Record<string, T>) => Object.fromEntries(Object.entries(v).map(([k, val]) => [k === name ? to : k, val]));\n\tfor (const [id, v] of edit.values) edit.values.set(id, rename(v));\n\tconst updates = writeVariantEdit(edit);\n\tupdateInstanceValues(g, setId, updates, (props) => (name in props ? rename(props) : props));\n\treturn done(nodes, nodes.map((n) => updates.get(n.id) ?? n), [setId], options);\n}\n\n/** Renames a value of a variant property across a set: every variant using it, and instances. */\nexport function renameCanvasVariantValue<TNode extends CanvasNode>(\n\tnodes: readonly TNode[],\n\tsetId: string,\n\tproperty: string,\n\tvalue: string,\n\tnewValue: string,\n\toptions: CanvasComponentSyncOptions = {}\n): CanvasComponentResult<TNode> {\n\tconst g = new Graph(nodes);\n\tconst edit = variantEditIn(g, setId);\n\tif (!('set' in edit)) return fail(nodes, edit.code, edit.reason);\n\tconst option = variantOptionsIn(g, setId).find((o) => o.name === property);\n\tif (!option) return fail(nodes, 'unknown-property', `Unknown variant property \"${property}\".`);\n\tif (!option.values.includes(value)) return fail(nodes, 'invalid', `\"${property}\" has no value \"${value}\".`);\n\tconst to = newValue.trim();\n\tconst problem = variantNameProblem('value', to);\n\tif (problem) return fail(nodes, 'invalid', problem);\n\tif (to === value) return { ok: true, nodes: nodes as TNode[], ids: [setId] };\n\tconst touched: string[] = [];\n\tfor (const [id, v] of edit.values) {\n\t\tif (v[property] !== value) continue;\n\t\tedit.values.set(id, { ...v, [property]: to });\n\t\ttouched.push(id);\n\t}\n\tconst dup = duplicateAmong(edit.values, touched);\n\tif (dup) return fail(nodes, 'invalid', `A variant with ${dup} exists already.`);\n\tconst updates = writeVariantEdit(edit);\n\tupdateInstanceValues(g, setId, updates, (props) => (props[property] === value ? { ...props, [property]: to } : props));\n\treturn done(nodes, nodes.map((n) => updates.get(n.id) ?? n), [setId], options);\n}\n","// Portions ported from figcraft/crates/engine/src/cmd/component.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport type { ArrangeHistoryTarget } from './arrangeController.js';\nimport {\n\taddCanvasVariant,\n\tbindCanvasComponentProperty,\n\tcombineCanvasComponentsAsVariants,\n\tcreateCanvasComponent,\n\tcreateCanvasInstance,\n\tdetachCanvasInstance,\n\tgetInstanceData,\n\tgetNodeOverrides,\n\tgetOwningInstance,\n\tgetRootInstance,\n\tgetVariantSet,\n\tinspectCanvasInstance,\n\tisCanvasComponent,\n\tisCanvasComponentSet,\n\tisInsideInstance,\n\tlistCanvasComponents,\n\tpushCanvasInstanceOverrides,\n\tremoveCanvasVariant,\n\trenameCanvasVariantProperty,\n\trenameCanvasVariantValue,\n\tresetCanvasInstanceOverrides,\n\trestoreCanvasMainComponent,\n\tsetCanvasComponentProperty,\n\tsetCanvasInstanceProperties,\n\tsetCanvasVariantProperties,\n\tswapCanvasInstance,\n\tsyncAllCanvasInstances,\n\tsyncCanvasComponents,\n\twouldCreateComponentCycle,\n\ttype AddCanvasVariantOptions,\n\ttype CanvasComponentPropertyDef,\n\ttype CanvasComponentPropertyField,\n\ttype CanvasComponentResult,\n\ttype CanvasComponentSyncOptions,\n\ttype CreateCanvasComponentOptions,\n\ttype CreateCanvasInstanceOptions\n} from './components.js';\nimport type { CanvasNode } from './types.js';\n\nexport interface CanvasComponentsControllerOptions<TNode extends CanvasNode = CanvasNode> extends CanvasComponentSyncOptions {\n\tgetNodes: () => TNode[];\n\tsetNodes: (nodes: TNode[]) => void;\n\t/** When set, every component action is one undoable step. */\n\thistory?: ArrangeHistoryTarget;\n\t/** The selection (default: nodes with `selected`). */\n\tgetSelectedIds?: () => string[];\n\t/** Called by `goToMainComponent` with the main (e.g. to pan the viewport to it). */\n\tonReveal?: (node: TNode) => void;\n}\n\n/** One side of an undo step: nodes to put (or remove, `null`) and the array order to restore. */\ninterface NodePatch {\n\tchanges: Map<string, CanvasNode | null>;\n\torder: string[];\n}\n\nfunction makePatch(from: readonly CanvasNode[], to: readonly CanvasNode[]): NodePatch {\n\tconst fromIds = new Map(from.map((n) => [n.id, n]));\n\tconst toIds = new Set(to.map((n) => n.id));\n\tconst changes = new Map<string, CanvasNode | null>();\n\tfor (const n of to) if (fromIds.get(n.id) !== n) changes.set(n.id, n);\n\tfor (const n of from) if (!toIds.has(n.id)) changes.set(n.id, null);\n\treturn { changes, order: to.map((n) => n.id) };\n}\n\n/** Applies a patch to the current nodes, keeping nodes it doesn't name as they are now. */\nfunction applyPatch<TNode extends CanvasNode>(current: readonly TNode[], patch: NodePatch): TNode[] {\n\tconst byId = new Map<string, CanvasNode>(current.map((n) => [n.id, n]));\n\tfor (const [id, n] of patch.changes) {\n\t\tif (n) byId.set(id, n);\n\t\telse byId.delete(id);\n\t}\n\tconst out: CanvasNode[] = [];\n\tconst placed = new Set<string>();\n\tfor (const id of patch.order) {\n\t\tconst n = byId.get(id);\n\t\tif (n && !placed.has(id)) {\n\t\t\tout.push(n);\n\t\t\tplaced.add(id);\n\t\t}\n\t}\n\tfor (const n of current) if (!placed.has(n.id) && byId.has(n.id)) out.push(byId.get(n.id)!);\n\treturn out as TNode[];\n}\n\n/**\n * Framework-agnostic components controller shared by the React hook and the Svelte rune. Its\n * actions (create component / instance, combine as variants, add / remove / edit / rename\n * variants, detach, reset, push, swap, set properties, restore main) are one undoable step each with a `history`; `sync()` keeps\n * instances following their mains after any other edit (call it after the nodes change).\n * Each `can…` check returns `true` or the reason the action can't run.\n */\nexport function createCanvasComponentsController<TNode extends CanvasNode = CanvasNode>(options: CanvasComponentsControllerOptions<TNode>) {\n\tlet synced: TNode[] | undefined;\n\tconst syncOptions = (): CanvasComponentSyncOptions => ({\n\t\tidGenerator: options.idGenerator,\n\t\ttextTypes: options.textTypes,\n\t\tlayout: options.layout,\n\t\ttokens: options.tokens\n\t});\n\tconst selection = (ids?: Iterable<string>): string[] =>\n\t\tids ? [...ids] : options.getSelectedIds?.() ?? options.getNodes().filter((n) => n.selected).map((n) => n.id);\n\tconst target = (id?: string) => id ?? selection()[0];\n\n\tfunction set(next: TNode[]) {\n\t\tsynced = next;\n\t\toptions.setNodes(next);\n\t}\n\n\t/** Syncs edits made since the last sync (overrides, main edits) and returns the nodes. */\n\tfunction sync(): TNode[] {\n\t\tconst nodes = options.getNodes();\n\t\tif (synced === undefined || nodes === synced) {\n\t\t\tsynced = nodes;\n\t\t\treturn nodes;\n\t\t}\n\t\tconst next = syncCanvasComponents(synced, nodes, syncOptions());\n\t\tsynced = next;\n\t\tif (next !== nodes) options.setNodes(next);\n\t\treturn next;\n\t}\n\n\tfunction run(name: string, op: (nodes: TNode[]) => CanvasComponentResult<TNode>): CanvasComponentResult<TNode> {\n\t\tconst before = sync();\n\t\tconst result = op(before);\n\t\tif (!result.ok || result.nodes === before) return result;\n\t\tconst forward = makePatch(before, result.nodes);\n\t\tconst backward = makePatch(result.nodes, before);\n\t\tconst execute = () => set(applyPatch(options.getNodes(), forward));\n\t\tconst undo = () => set(applyPatch(options.getNodes(), backward));\n\t\tif (options.history) options.history.execute({ name, execute, undo });\n\t\telse execute();\n\t\treturn result;\n\t}\n\n\tconst reason = (result: CanvasComponentResult<TNode>): true | string => (result.ok ? true : result.reason);\n\tconst nodes = () => options.getNodes();\n\tconst refused = (why: string) => ({ ok: false, nodes: nodes(), code: 'not-found', reason: why }) as CanvasComponentResult<TNode>;\n\t/** The set `id` names (a set, or a variant's set; default: the selection's). */\n\tconst setOf = (id?: string): string | undefined => {\n\t\tconst at = target(id);\n\t\tconst node = at ? nodes().find((n) => n.id === at) : undefined;\n\t\tif (!node) return undefined;\n\t\treturn isCanvasComponentSet(node) ? node.id : getVariantSet(nodes(), node.id);\n\t};\n\t/** The variant `id` names (default: the first selected variant). */\n\tconst variantOf = (id?: string): string | undefined => {\n\t\tconst at = target(id);\n\t\treturn at && getVariantSet(nodes(), at) ? at : undefined;\n\t};\n\tconst NO_SET = 'Select a component set or one of its variants.';\n\tconst NO_VARIANT = 'Select a variant of a component set.';\n\ttype VariantOptions = Omit<AddCanvasVariantOptions, keyof CanvasComponentSyncOptions>;\n\t/** `addVariant` options with `from` defaulting to the selected variant. */\n\tconst addOptions = (setId: string | undefined, addOptions_: VariantOptions): VariantOptions => {\n\t\tif (addOptions_.from !== undefined || setId !== undefined) return addOptions_;\n\t\tconst v = variantOf();\n\t\treturn v ? { ...addOptions_, from: v } : addOptions_;\n\t};\n\n\tconst controller = {\n\t\tsync,\n\t\t/** Rebuilds every instance from its main (e.g. after loading), not recorded in history. */\n\t\tsyncAll(): TNode[] {\n\t\t\tconst next = syncAllCanvasInstances(nodes(), syncOptions());\n\t\t\tif (next !== nodes()) set(next);\n\t\t\telse synced = next;\n\t\t\treturn next;\n\t\t},\n\n\t\t/** Create Component from `ids` (default: the selection). */\n\t\tcreateComponent(ids?: Iterable<string>, createOptions: Omit<CreateCanvasComponentOptions, keyof CanvasComponentSyncOptions> = {}) {\n\t\t\tconst list = selection(ids);\n\t\t\treturn run('Create Component', (n) => createCanvasComponent(n, list, { ...syncOptions(), ...createOptions }));\n\t\t},\n\t\tcanCreateComponent: (ids?: Iterable<string>) => reason(createCanvasComponent(nodes(), selection(ids), { layout: false })),\n\n\t\t/** Combine as Variants: wraps the selected components in a component set. */\n\t\tcombineAsVariants(ids?: Iterable<string>, combineOptions: { id?: string; name?: string; padding?: number } = {}) {\n\t\t\tconst list = selection(ids);\n\t\t\treturn run('Combine as Variants', (n) => combineCanvasComponentsAsVariants(n, list, { ...syncOptions(), ...combineOptions }));\n\t\t},\n\t\tcanCombineAsVariants: (ids?: Iterable<string>) => reason(combineCanvasComponentsAsVariants(nodes(), selection(ids), { layout: false })),\n\n\t\t/** Create Instance of a component or component set. */\n\t\tcreateInstance(componentId: string, createOptions: Omit<CreateCanvasInstanceOptions, keyof CanvasComponentSyncOptions> = {}) {\n\t\t\treturn run('Create Instance', (n) => createCanvasInstance(n, componentId, { ...syncOptions(), ...createOptions }));\n\t\t},\n\t\tcanCreateInstance(componentId?: string, parentId?: string): true | string {\n\t\t\tconst list = nodes();\n\t\t\tconst target = componentId ? list.find((n) => n.id === componentId) : undefined;\n\t\t\tif (!target || (!isCanvasComponent(target) && !isCanvasComponentSet(target))) return 'Choose a component or component set.';\n\t\t\tif (wouldCreateComponentCycle(list, componentId!, parentId)) return \"An instance can't be placed inside its own main component.\";\n\t\t\tif (parentId && getOwningInstance(list, parentId)) return \"Instances can't gain layers: place it outside the instance.\";\n\t\t\treturn true;\n\t\t},\n\n\t\t/** Detach Instance (default: the selected instances). */\n\t\tdetachInstance(ids?: Iterable<string>) {\n\t\t\tconst list = selection(ids);\n\t\t\treturn run('Detach Instance', (n) => detachCanvasInstance(n, list, syncOptions()));\n\t\t},\n\t\tcanDetachInstance(ids?: Iterable<string>): true | string {\n\t\t\tconst list = nodes();\n\t\t\tconst instances = selection(ids).filter((id) => getInstanceData(list.find((n) => n.id === id)));\n\t\t\tif (instances.length === 0) return 'Select an instance.';\n\t\t\tif (instances.some((id) => isInsideInstance(list, id))) return \"A nested instance can't be detached on its own: detach the outer instance first.\";\n\t\t\treturn true;\n\t\t},\n\n\t\t/** Reset overrides of an instance or layer (default: the first selected); `group` resets one. */\n\t\tresetOverrides(id?: string, group?: string) {\n\t\t\tconst at = target(id);\n\t\t\tif (!at) return { ok: false, nodes: nodes(), code: 'not-found', reason: 'Select an instance or a layer inside one.' } as CanvasComponentResult<TNode>;\n\t\t\treturn run(group ? 'Reset Override' : 'Reset All Changes', (n) => resetCanvasInstanceOverrides(n, at, group, syncOptions()));\n\t\t},\n\t\tcanResetOverrides(id?: string): true | string {\n\t\t\tconst at = target(id);\n\t\t\tconst info = at ? inspectCanvasInstance(nodes(), at) : undefined;\n\t\t\tif (!info) return 'Select an instance or a layer inside one.';\n\t\t\tconst changedProps = !info.nested && info.properties.some((p) => p.value !== p.default);\n\t\t\tconst own = info.id === at ? info.overrides.length > 0 : getNodeOverrides(nodes().find((n) => n.id === at)).length > 0;\n\t\t\treturn own || (info.id === at && changedProps) ? true : 'No overrides to reset.';\n\t\t},\n\n\t\t/** Push the (outermost) instance's overrides to its main component. */\n\t\tpushOverrides(id?: string) {\n\t\t\tconst at = target(id);\n\t\t\tif (!at) return { ok: false, nodes: nodes(), code: 'not-found', reason: 'Select an instance.' } as CanvasComponentResult<TNode>;\n\t\t\treturn run('Push Changes to Main Component', (n) => pushCanvasInstanceOverrides(n, at, syncOptions()));\n\t\t},\n\t\tcanPushOverrides(id?: string): true | string {\n\t\t\tconst at = target(id);\n\t\t\treturn at ? reason(pushCanvasInstanceOverrides(nodes(), at, { layout: false })) : 'Select an instance.';\n\t\t},\n\n\t\t/** Swap the selected instances to another component (or a set's default variant). */\n\t\tswapInstance(componentId: string, ids?: Iterable<string>, swapOptions: { keepTextOverridesOnly?: boolean } = {}) {\n\t\t\tconst list = selection(ids);\n\t\t\treturn run('Swap Instance', (n) => swapCanvasInstance(n, list, componentId, { ...syncOptions(), ...swapOptions }));\n\t\t},\n\t\tcanSwapInstance(componentId?: string, ids?: Iterable<string>): true | string {\n\t\t\tconst list = nodes();\n\t\t\tconst instances = selection(ids).filter((id) => getInstanceData(list.find((n) => n.id === id)));\n\t\t\tif (instances.length === 0) return 'Select an instance.';\n\t\t\tif (componentId === undefined) return true;\n\t\t\tconst main = list.find((n) => n.id === componentId);\n\t\t\tif (!main || (!isCanvasComponent(main) && !isCanvasComponentSet(main))) return 'Choose a component or component set.';\n\t\t\tfor (const id of instances) {\n\t\t\t\tconst node = list.find((n) => n.id === id)!;\n\t\t\t\tif (wouldCreateComponentCycle(list, componentId, node.parentId)) return \"An instance can't show the component it is inside.\";\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\n\t\t/** Set variant / component property values on the selected instances (one step). */\n\t\tsetInstanceProperties(properties: Record<string, unknown>, ids?: Iterable<string>) {\n\t\t\tconst list = selection(ids);\n\t\t\treturn run('Set Instance Properties', (n) => setCanvasInstanceProperties(n, list, properties, syncOptions()));\n\t\t},\n\t\tcanSetInstanceProperties(ids?: Iterable<string>): true | string {\n\t\t\tconst list = nodes();\n\t\t\tconst sel = selection(ids);\n\t\t\treturn sel.length > 0 && sel.every((id) => getInstanceData(list.find((n) => n.id === id))) ? true : 'Select instances.';\n\t\t},\n\n\t\t/** Restore Main Component from an instance whose main was deleted. */\n\t\trestoreMainComponent(id?: string) {\n\t\t\tconst at = target(id);\n\t\t\tif (!at) return { ok: false, nodes: nodes(), code: 'not-found', reason: 'Select an instance.' } as CanvasComponentResult<TNode>;\n\t\t\treturn run('Restore Main Component', (n) => restoreCanvasMainComponent(n, at, syncOptions()));\n\t\t},\n\t\tcanRestoreMainComponent(id?: string): true | string {\n\t\t\tconst at = target(id);\n\t\t\treturn at ? reason(restoreCanvasMainComponent(nodes(), at, { layout: false })) : 'Select an instance.';\n\t\t},\n\n\t\t/** Adds, edits or (with `null`) deletes a component property. */\n\t\tsetComponentProperty(componentId: string, name: string, def: CanvasComponentPropertyDef | null, propertyOptions: { newName?: string } = {}) {\n\t\t\treturn run(def ? 'Edit Component Property' : 'Delete Component Property', (n) =>\n\t\t\t\tsetCanvasComponentProperty(n, componentId, name, def, { ...syncOptions(), ...propertyOptions })\n\t\t\t);\n\t\t},\n\n\t\t/**\n\t\t * Adds a variant to a set (default: the selection's set) by duplicating `from` (default:\n\t\t * the selected variant, else the default variant) with the given `variant` values.\n\t\t */\n\t\taddVariant(setId?: string, variantOptions: VariantOptions = {}) {\n\t\t\tconst set = setOf(setId);\n\t\t\tif (!set) return refused(NO_SET);\n\t\t\tconst opts = addOptions(setId, variantOptions);\n\t\t\treturn run('Add Variant', (n) => addCanvasVariant(n, set, { ...syncOptions(), ...opts }));\n\t\t},\n\t\tcanAddVariant(setId?: string, variantOptions: VariantOptions = {}): true | string {\n\t\t\tconst set = setOf(setId);\n\t\t\treturn set ? reason(addCanvasVariant(nodes(), set, { ...addOptions(setId, variantOptions), layout: false })) : NO_SET;\n\t\t},\n\n\t\t/** Removes a variant (default: the selected one); its instances switch to the closest variant. */\n\t\tremoveVariant(variantId?: string) {\n\t\t\tconst v = variantOf(variantId);\n\t\t\tif (!v) return refused(NO_VARIANT);\n\t\t\treturn run('Remove Variant', (n) => removeCanvasVariant(n, v, syncOptions()));\n\t\t},\n\t\tcanRemoveVariant(variantId?: string): true | string {\n\t\t\tconst v = variantOf(variantId);\n\t\t\treturn v ? reason(removeCanvasVariant(nodes(), v, { layout: false })) : NO_VARIANT;\n\t\t},\n\n\t\t/** Edits a variant's property values (default: the selected variant). */\n\t\tsetVariantProperties(values: Record<string, string>, variantId?: string, variantOptions: { defaultValue?: string } = {}) {\n\t\t\tconst v = variantOf(variantId);\n\t\t\tif (!v) return refused(NO_VARIANT);\n\t\t\treturn run('Edit Variant', (n) => setCanvasVariantProperties(n, v, values, { ...syncOptions(), ...variantOptions }));\n\t\t},\n\t\tcanSetVariantProperties(values?: Record<string, string>, variantId?: string): true | string {\n\t\t\tconst v = variantOf(variantId);\n\t\t\tif (!v) return NO_VARIANT;\n\t\t\treturn values ? reason(setCanvasVariantProperties(nodes(), v, values, { layout: false })) : true;\n\t\t},\n\n\t\t/** Renames a variant property across a set (default: the selection's set). */\n\t\trenameVariantProperty(name: string, newName: string, setId?: string) {\n\t\t\tconst set = setOf(setId);\n\t\t\tif (!set) return refused(NO_SET);\n\t\t\treturn run('Rename Variant Property', (n) => renameCanvasVariantProperty(n, set, name, newName, syncOptions()));\n\t\t},\n\t\tcanRenameVariantProperty(name?: string, newName?: string, setId?: string): true | string {\n\t\t\tconst set = setOf(setId);\n\t\t\tif (!set) return NO_SET;\n\t\t\treturn name !== undefined && newName !== undefined ? reason(renameCanvasVariantProperty(nodes(), set, name, newName, { layout: false })) : true;\n\t\t},\n\n\t\t/** Renames a value of a variant property across a set (default: the selection's set). */\n\t\trenameVariantValue(property: string, value: string, newValue: string, setId?: string) {\n\t\t\tconst set = setOf(setId);\n\t\t\tif (!set) return refused(NO_SET);\n\t\t\treturn run('Rename Variant Value', (n) => renameCanvasVariantValue(n, set, property, value, newValue, syncOptions()));\n\t\t},\n\t\tcanRenameVariantValue(property?: string, value?: string, newValue?: string, setId?: string): true | string {\n\t\t\tconst set = setOf(setId);\n\t\t\tif (!set) return NO_SET;\n\t\t\treturn property !== undefined && value !== undefined && newValue !== undefined\n\t\t\t\t? reason(renameCanvasVariantValue(nodes(), set, property, value, newValue, { layout: false }))\n\t\t\t\t: true;\n\t\t},\n\n\t\t/** Binds (or with `null` unbinds) a main layer's field to a component property. */\n\t\tbindComponentProperty(layerId: string, field: CanvasComponentPropertyField, name: string | null) {\n\t\t\treturn run('Bind Component Property', (n) => bindCanvasComponentProperty(n, layerId, field, name, syncOptions()));\n\t\t},\n\n\t\t/** Selects the main component of an instance (or of the instance a layer is in). */\n\t\tgoToMainComponent(id?: string): string | undefined {\n\t\t\tconst at = target(id);\n\t\t\tconst list = sync();\n\t\t\tconst inst = at ? getOwningInstance(list, at) : undefined;\n\t\t\tconst main = inst ? getInstanceData(list.find((n) => n.id === inst))?.componentId : undefined;\n\t\t\tconst node = main ? list.find((n) => n.id === main) : undefined;\n\t\t\tif (!node) return undefined;\n\t\t\tconst next = list.map((n) => (!!n.selected === (n.id === main) ? n : ({ ...n, selected: n.id === main } as TNode)));\n\t\t\tset(next);\n\t\t\toptions.onReveal?.(next.find((n) => n.id === main)!);\n\t\t\treturn main;\n\t\t},\n\t\tcanGoToMainComponent(id?: string): true | string {\n\t\t\tconst at = target(id);\n\t\t\tconst list = nodes();\n\t\t\tconst inst = at ? getOwningInstance(list, at) : undefined;\n\t\t\tif (!inst) return 'Select an instance.';\n\t\t\tconst main = getInstanceData(list.find((n) => n.id === inst))!.componentId;\n\t\t\treturn list.some((n) => n.id === main) ? true : 'The main component was deleted.';\n\t\t},\n\n\t\t/** Components and component sets, for a components panel. */\n\t\tlist: () => listCanvasComponents(nodes()),\n\t\t/** The instance inspector's view of an instance or a layer inside one (default: the selection). */\n\t\tinspect: (id?: string) => {\n\t\t\tconst at = target(id);\n\t\t\treturn at ? inspectCanvasInstance(nodes(), at) : undefined;\n\t\t},\n\t\t/** The outermost instance containing `id`. */\n\t\trootInstanceOf: (id: string) => getRootInstance(nodes(), id)\n\t};\n\treturn controller;\n}\n\nexport type CanvasComponentsController<TNode extends CanvasNode = CanvasNode> = ReturnType<typeof createCanvasComponentsController<TNode>>;\n\n/** Drag data type for dragging a component from a components panel onto a canvas. */\nexport const CANVAS_COMPONENT_DRAG_TYPE = 'application/x-pixerate-component';\n\n/** Reads the component id a components panel put on a drag (or `undefined`). */\nexport function getCanvasComponentDragId(dataTransfer: Pick<DataTransfer, 'getData'> | null | undefined): string | undefined {\n\tconst id = dataTransfer?.getData(CANVAS_COMPONENT_DRAG_TYPE);\n\treturn id ? id : undefined;\n}\n\n","/**\n * Freehand drawing utilities: stroke smoothing, cubic Bézier conversion,\n * ShapeNode generation, and spatial eraser hit-testing within the xyflow framework.\n */\nimport type { CanvasNode, Point, Rect } from './types.js';\nimport { fitFreehandPath, type FitPathDataOptions } from './curveFit.js';\nimport { distanceToBezierPath } from './pathops/bezier.js';\nimport { getNodeShape } from './shapeNodes.js';\nimport { getNodeRect } from './arrange.js';\n\nexport type FreehandTool = 'select' | 'pen' | 'highlighter' | 'eraser';\n\nimport type { CanvasGradientDef } from './gradient.js';\nimport { brushstrokeSeed, profileAlongPolyline } from './brushstroke.js';\nimport type { CanvasBrush, CanvasBrushPaint, CanvasBrushstroke } from './brushTypes.js';\n\nexport interface FreehandStrokeStyle {\n\t/** Stroke color (any CSS color string) or gradient definition. */\n\tcolor: string | CanvasGradientDef;\n\t/** Stroke width in flow canvas units. */\n\twidth: number;\n\t/** Stroke opacity (0 to 1). */\n\topacity: number;\n\t/** SVG stroke-linecap: 'round', 'butt', or 'square'. */\n\tcap?: 'round' | 'butt' | 'square';\n\t/** SVG stroke-linejoin: 'round', 'bevel', or 'miter'. */\n\tjoin?: 'round' | 'bevel' | 'miter';\n\t/** Optional gradient definition for the stroke. */\n\tgradient?: CanvasGradientDef;\n\t/**\n\t * Paint the stroke with a dry paint brush (a preset id such as `ink-pen`, or tip settings)\n\t * instead of an SVG stroke; `width` is the brush size. Without it strokes are plain vector\n\t * paths, exactly as before.\n\t */\n\tbrush?: CanvasBrush;\n\t/**\n\t * Brush diameter in canvas units for brushed strokes. Default: the preset's own size (tip\n\t * settings: their `size`, else `width`). Set it when the user picks a size.\n\t */\n\tbrushSize?: number;\n\t/**\n\t * Gradient paint (or one colour) for a brushed stroke instead of `color`, sampled dab by dab\n\t * along the path; `color` still gives the alpha and fills `paint` stops. Only with `brush`.\n\t */\n\tpaint?: CanvasBrushPaint;\n}\n\n/** Pen data for one freehand point (Pointer Events: pressure 0..1, tilt in degrees). */\nexport interface FreehandPenSample {\n\tpressure?: number;\n\ttiltX?: number;\n\ttiltY?: number;\n}\n\nexport interface FreehandDrawOptions {\n\t/** Cubic curve fitting tolerance (default: 1.0). Lower is tighter to points. */\n\ttolerance?: number;\n\t/** Corner angle threshold in degrees for sharp corners (default: 40). */\n\tcornerAngleDeg?: number;\n\t/** Real-time point streamlining / smoothing factor in [0, 1] (default: 0.35). 0 disables smoothing. */\n\tstreamline?: number;\n}\n\n/** The Pointer Event fields `pointerPenSample` reads (structural; no DOM types). */\nexport interface FreehandPointerLike {\n\tpointerType?: string;\n\tpressure?: number;\n\ttiltX?: number;\n\ttiltY?: number;\n}\n\n/**\n * Pen data of a pointer event for `startStroke` / `continueStroke`: pressure and tilt from pens,\n * nothing from mice and touch (a mouse reports 0.5 while pressed, which is not pressure). A pen\n * reporting 0 pressure (some do on the first event) counts as full.\n */\nexport function pointerPenSample(e: FreehandPointerLike): FreehandPenSample | undefined {\n\tif (e.pointerType !== 'pen') return undefined;\n\tconst out: FreehandPenSample = {};\n\tif (typeof e.pressure === 'number' && e.pressure > 0) out.pressure = Math.min(1, e.pressure);\n\tif (typeof e.tiltX === 'number' && e.tiltX !== 0) out.tiltX = e.tiltX;\n\tif (typeof e.tiltY === 'number' && e.tiltY !== 0) out.tiltY = e.tiltY;\n\treturn out;\n}\n\nexport interface FreehandStroke {\n\tid: string;\n\ttool: FreehandTool;\n\tpoints: Point[];\n\t/** Pen data per point (same length as `points`), when the input reported any. Used by brushed strokes. */\n\tsamples?: FreehandPenSample[];\n\tstyle: FreehandStrokeStyle;\n\tstartTime?: number;\n}\n\n/**\n * Returns default stroke styles tailored for specific drawing tools.\n */\nexport function createDefaultStrokeStyle(tool: FreehandTool): FreehandStrokeStyle {\n\tif (tool === 'highlighter') {\n\t\treturn {\n\t\t\tcolor: '#facc15',\n\t\t\twidth: 24,\n\t\t\topacity: 0.35,\n\t\t\tcap: 'round',\n\t\t\tjoin: 'round'\n\t\t};\n\t}\n\treturn {\n\t\tcolor: '#3b82f6',\n\t\twidth: 3,\n\t\topacity: 1.0,\n\t\tcap: 'round',\n\t\tjoin: 'round'\n\t};\n}\n\n/**\n * Smooths jittery pointer samples using an exponential moving average.\n * The first and last points are anchored to guarantee stroke start and end precision.\n */\nexport function streamlinePoints(points: readonly Point[], factor = 0.35): Point[] {\n\tif (points.length < 3 || factor <= 0) return [...points];\n\tconst clampedFactor = Math.min(Math.max(factor, 0), 0.95);\n\tconst smoothed: Point[] = [points[0]];\n\n\tfor (let i = 1; i < points.length - 1; i++) {\n\t\tconst prev = smoothed[i - 1];\n\t\tconst curr = points[i];\n\t\tconst x = prev[0] * clampedFactor + curr[0] * (1 - clampedFactor);\n\t\tconst y = prev[1] * clampedFactor + curr[1] * (1 - clampedFactor);\n\t\tsmoothed.push([x, y]);\n\t}\n\n\tsmoothed.push(points[points.length - 1]);\n\treturn smoothed;\n}\n\n/**\n * Computes the tight axis-aligned bounding box of a sequence of points,\n * optionally expanded by a margin (such as strokeWidth / 2).\n */\nexport function strokeBoundingBox(points: readonly Point[], margin = 0): Rect {\n\tif (points.length === 0) {\n\t\treturn { x: 0, y: 0, width: 0, height: 0 };\n\t}\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\n\tfor (const [x, y] of points) {\n\t\tif (x < minX) minX = x;\n\t\tif (y < minY) minY = y;\n\t\tif (x > maxX) maxX = x;\n\t\tif (y > maxY) maxY = y;\n\t}\n\n\tconst w = Math.max(maxX - minX, 0);\n\tconst h = Math.max(maxY - minY, 0);\n\n\treturn {\n\t\tx: minX - margin,\n\t\ty: minY - margin,\n\t\twidth: Math.max(w + margin * 2, 1),\n\t\theight: Math.max(h + margin * 2, 1)\n\t};\n}\n\n/**\n * Converts a sequence of points into an SVG path `d` string.\n * Gracefully handles single points (dots) and two-point line segments.\n */\nexport function strokePointsToPathData(\n\tpoints: readonly Point[],\n\toptions: Omit<FitPathDataOptions, 'closed'> = {}\n): string {\n\tif (points.length === 0) return '';\n\tif (points.length === 1) {\n\t\tconst [x, y] = points[0];\n\t\t// Small delta with round cap renders as a circular dot\n\t\treturn `M ${x} ${y} L ${x + 0.01} ${y}`;\n\t}\n\tif (points.length === 2) {\n\t\tconst [p0, p1] = points;\n\t\treturn `M ${p0[0]} ${p0[1]} L ${p1[0]} ${p1[1]}`;\n\t}\n\tconst fitted = fitFreehandPath(points, options);\n\treturn fitted.length > 0 ? fitted : `M ${points[0][0]} ${points[0][1]}`;\n}\n\nexport interface StrokeToShapeNodeOptions {\n\tid?: string;\n\ttolerance?: number;\n\tcornerAngleDeg?: number;\n\t/** Values per pen profile of a brushed stroke (default 32). */\n\tprofileLength?: number;\n}\n\n/**\n * The brush of a brushed stroke node: the style's brush plus pressure and tilt profiles along the\n * points, from the stroke's pen samples. Profiles that carry nothing (no pressure reported, an\n * upright pen) are left out.\n */\nexport function freehandBrushstroke(\n\tbrush: CanvasBrush,\n\tpoints: readonly Point[],\n\tsamples: readonly FreehandPenSample[] | undefined,\n\tprofileLength = 32\n): CanvasBrushstroke {\n\tconst out: CanvasBrushstroke = { brush };\n\tif (!samples || samples.length !== points.length) return out;\n\tconst finite = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v);\n\tif (samples.some((s) => finite(s.pressure))) {\n\t\tconst pressure = profileAlongPolyline(points, samples.map((s) => (finite(s.pressure) ? Math.min(1, Math.max(0, s.pressure)) : 1)), profileLength);\n\t\tif (pressure.some((p) => p !== 1)) out.pressure = pressure;\n\t}\n\tfor (const key of ['tiltX', 'tiltY'] as const) {\n\t\tif (!samples.some((s) => finite(s[key]) && s[key] !== 0)) continue;\n\t\tout[key] = profileAlongPolyline(points, samples.map((s) => (finite(s[key]) ? Math.min(90, Math.max(-90, s[key])) : 0)), profileLength);\n\t}\n\treturn out;\n}\n\n/**\n * Converts a completed freehand stroke into a native xyflow `ShapeNode`.\n * The geometry is normalized to node-local space with `viewBox`, ensuring crisp\n * scaling, non-scaling strokes, and full compatibility with existing shape ops.\n */\nexport function strokeToShapeNode(\n\tstroke: FreehandStroke,\n\toptions: StrokeToShapeNodeOptions = {}\n): CanvasNode {\n\tconst points = stroke.points;\n\t// A brushed stroke with a chosen size is that wide (the eraser and bounds read strokeWidth).\n\tconst strokeWidth = (stroke.style.brush !== undefined && stroke.style.brushSize) || stroke.style.width || 3;\n\tconst bounds = strokeBoundingBox(points, 0);\n\n\t// Normalize points to node-local origin (0, 0)\n\tconst localPoints = points.map(\n\t\t([x, y]) => [x - bounds.x, y - bounds.y] as Point\n\t);\n\n\tconst d = strokePointsToPathData(localPoints, {\n\t\ttolerance: options.tolerance ?? 1.0,\n\t\tcornerAngleDeg: options.cornerAngleDeg ?? 40\n\t});\n\n\tconst width = Math.max(bounds.width, 1);\n\tconst height = Math.max(bounds.height, 1);\n\n\tconst strokePaint = stroke.style.gradient ?? stroke.style.color;\n\tconst strokeGradient =\n\t\tstroke.style.gradient ?? (typeof stroke.style.color === 'object' && stroke.style.color ? stroke.style.color : undefined);\n\n\treturn {\n\t\tid: options.id ?? stroke.id,\n\t\ttype: 'shape',\n\t\tposition: { x: bounds.x, y: bounds.y },\n\t\twidth,\n\t\theight,\n\t\tstyle: {\n\t\t\twidth,\n\t\t\theight\n\t\t},\n\t\tdata: {\n\t\t\tshape: {\n\t\t\t\tkind: 'path',\n\t\t\t\td,\n\t\t\t\tviewBox: [0, 0, width, height],\n\t\t\t\tstroke: strokePaint,\n\t\t\t\tstrokeWidth,\n\t\t\t\topacity: stroke.style.opacity,\n\t\t\t\tstrokeLinecap: stroke.style.cap ?? 'round',\n\t\t\t\tstrokeLinejoin: stroke.style.join ?? 'round',\n\t\t\t\t...(strokeGradient ? { strokeGradient } : {}),\n\t\t\t\t...(stroke.style.brush !== undefined\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tbrush: {\n\t\t\t\t\t\t\t\t...freehandBrushstroke(stroke.style.brush, localPoints, stroke.samples, options.profileLength),\n\t\t\t\t\t\t\t\t...(stroke.style.paint !== undefined ? { paint: stroke.style.paint } : {}),\n\t\t\t\t\t\t\t\t// New strokes carry a character seed (jitter and grain) and their size, if chosen.\n\t\t\t\t\t\t\t\tseed: brushstrokeSeed(options.id ?? stroke.id),\n\t\t\t\t\t\t\t\t...(stroke.style.brushSize !== undefined && stroke.style.brushSize > 0 ? { size: stroke.style.brushSize } : {})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t\tfill: 'none'\n\t\t\t},\n\t\t\ttool: stroke.tool\n\t\t}\n\t};\n}\n\n/**\n * Finds all stroke shape nodes intersecting a given point within a hit radius.\n * Ideal for object eraser interactions.\n */\nexport function findStrokesAtPoint(\n\tpoint: Point,\n\tnodes: readonly CanvasNode[],\n\tradius = 12\n): CanvasNode[] {\n\tconst matches: CanvasNode[] = [];\n\n\tfor (const node of nodes) {\n\t\tconst shape = getNodeShape(node);\n\t\tif (!shape || shape.kind !== 'path') continue;\n\n\t\tconst rect = getNodeRect(node);\n\t\tconst strokeWidth = shape.strokeWidth ?? 2;\n\t\tconst effectiveRadius = radius + strokeWidth / 2;\n\n\t\t// Fast AABB rejection test\n\t\tif (\n\t\t\tpoint[0] < rect.x - effectiveRadius ||\n\t\t\tpoint[0] > rect.x + rect.width + effectiveRadius ||\n\t\t\tpoint[1] < rect.y - effectiveRadius ||\n\t\t\tpoint[1] > rect.y + rect.height + effectiveRadius\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Local coordinates relative to node origin\n\t\tconst localPoint: Point = [point[0] - rect.x, point[1] - rect.y];\n\n\t\t// Exact distance check to path contours\n\t\ttry {\n\t\t\tconst dist = distanceToBezierPath(shape.d, localPoint);\n\t\t\tif (dist <= effectiveRadius) {\n\t\t\t\tmatches.push(node);\n\t\t\t}\n\t\t} catch {\n\t\t\t// If path is malformed, fall back to AABB inside check\n\t\t\tif (\n\t\t\t\tpoint[0] >= rect.x &&\n\t\t\t\tpoint[0] <= rect.x + rect.width &&\n\t\t\t\tpoint[1] >= rect.y &&\n\t\t\t\tpoint[1] <= rect.y + rect.height\n\t\t\t) {\n\t\t\t\tmatches.push(node);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn matches;\n}\n","/**\n * Headless controller managing freehand drawing lifecycle, tool selection,\n * real-time pointer capture, ShapeNode creation, and undoable history actions.\n */\nimport type { ArrangeHistoryTarget } from './arrangeController.js';\nimport type { CanvasNode, Point } from './types.js';\nimport { isLocked } from './arrange.js';\nimport {\n\tcreateDefaultStrokeStyle,\n\tfindStrokesAtPoint,\n\tstreamlinePoints,\n\tstrokeToShapeNode,\n\ttype FreehandDrawOptions,\n\ttype FreehandPenSample,\n\ttype FreehandStroke,\n\ttype FreehandStrokeStyle,\n\ttype FreehandTool\n} from './freehand.js';\nimport { listCanvasBrushes, loadCanvasBrushstrokeRenderer, validateCanvasBrush, type CanvasBrush, type CanvasBrushPaint } from './brushstroke.js';\n\n/** A stroke drawn by code or an agent (`drawStroke`). */\nexport interface DrawStrokeInput {\n\t/** Points in canvas (flow) coordinates, at least one. */\n\tpoints: readonly Point[];\n\t/** `pen` (default) or `highlighter`: picks the default style. */\n\ttool?: 'pen' | 'highlighter';\n\t/** Style over the tool's defaults (colour, width, opacity, cap, join, brush, paint). */\n\tstyle?: Partial<FreehandStrokeStyle>;\n\t/** Pressure 0..1 spread evenly along the stroke (one value is constant). Brushed strokes only. */\n\tpressure?: readonly number[];\n\t/** Pen tilt in degrees (−90..90) spread evenly along the stroke. Brushed strokes only. */\n\ttiltX?: readonly number[];\n\ttiltY?: readonly number[];\n\t/** Smooth the points before fitting (default false: agent points are taken as given). */\n\tstreamline?: boolean;\n}\n\nexport interface DrawStrokeResult<TNode extends CanvasNode = CanvasNode> {\n\tid: string;\n\tnode: TNode;\n\t/** The new node's box in canvas coordinates. */\n\tbounds: { x: number; y: number; width: number; height: number };\n\t/** The brush of a brushed stroke, with the profiles it stored. */\n\tbrush?: unknown;\n}\n\nexport interface CanvasDrawControllerOptions<TNode extends CanvasNode = CanvasNode> {\n\tgetNodes: () => TNode[];\n\tsetNodes: (nodes: TNode[]) => void;\n\thistory?: ArrangeHistoryTarget;\n\ttool?: FreehandTool;\n\tstyle?: Partial<FreehandStrokeStyle>;\n\tdrawOptions?: FreehandDrawOptions;\n\tgenerateNodeId?: () => string;\n\tonToolChange?: (tool: FreehandTool) => void;\n\tonStyleChange?: (style: FreehandStrokeStyle) => void;\n\tonActiveStrokeChange?: (stroke: FreehandStroke | null) => void;\n\tonStrokeComplete?: (node: TNode) => void;\n\tonNodesErase?: (erasedNodes: TNode[]) => void;\n}\n\nexport function createCanvasDrawController<TNode extends CanvasNode = CanvasNode>(\n\toptions: CanvasDrawControllerOptions<TNode>\n) {\n\tlet currentTool: FreehandTool = options.tool ?? 'select';\n\tlet currentStyle: FreehandStrokeStyle = {\n\t\t...createDefaultStrokeStyle(currentTool),\n\t\t...options.style\n\t};\n\tlet activeStroke: FreehandStroke | null = null;\n\tconst erasedInDrag = new Map<string, { node: TNode; index: number }>();\n\n\tlet dragStartNodes: TNode[] | null = null;\n\n\tfunction setTool(tool: FreehandTool) {\n\t\tif (currentTool === tool) return;\n\t\tcancelStroke();\n\t\tcurrentTool = tool;\n\t\t// Update default styles when switching between pen and highlighter\n\t\tif (tool === 'pen' || tool === 'highlighter') {\n\t\t\tcurrentStyle = {\n\t\t\t\t...createDefaultStrokeStyle(tool),\n\t\t\t\t...options.style\n\t\t\t};\n\t\t\toptions.onStyleChange?.(currentStyle);\n\t\t}\n\t\toptions.onToolChange?.(tool);\n\t}\n\n\tfunction setStyle(style: Partial<FreehandStrokeStyle>) {\n\t\tcurrentStyle = { ...currentStyle, ...style };\n\t\toptions.onStyleChange?.(currentStyle);\n\t}\n\n\tfunction startStroke(point: Point, pen?: FreehandPenSample) {\n\t\tif (currentTool === 'select') return;\n\n\t\tif (currentTool === 'eraser') {\n\t\t\terasedInDrag.clear();\n\t\t\tdragStartNodes = [...options.getNodes()];\n\t\t\teraseAt(point);\n\t\t\treturn;\n\t\t}\n\n\t\tconst id =\n\t\t\toptions.generateNodeId?.() ??\n\t\t\t`stroke-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;\n\n\t\tactiveStroke = {\n\t\t\tid,\n\t\t\ttool: currentTool,\n\t\t\tpoints: [point],\n\t\t\t...(pen ? { samples: [{ ...pen }] } : {}),\n\t\t\tstyle: { ...currentStyle },\n\t\t\tstartTime: Date.now()\n\t\t};\n\n\t\toptions.onActiveStrokeChange?.(activeStroke);\n\t}\n\n\tfunction continueStroke(point: Point, pen?: FreehandPenSample) {\n\t\tif (currentTool === 'eraser') {\n\t\t\teraseAt(point);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!activeStroke) return;\n\n\t\tconst points = activeStroke.points;\n\t\tconst last = points[points.length - 1];\n\t\t// Discard identical or micro jitter points (sub-pixel)\n\t\tif (Math.hypot(point[0] - last[0], point[1] - last[1]) < 0.25) return;\n\n\t\tpoints.push(point);\n\t\tif (pen && !activeStroke.samples) activeStroke.samples = points.slice(0, -1).map(() => ({}));\n\t\tactiveStroke.samples?.push(pen ? { ...pen } : {});\n\t\toptions.onActiveStrokeChange?.({ ...activeStroke, points: [...points] });\n\t}\n\n\tfunction endStroke(): TNode | null {\n\t\tif (currentTool === 'eraser') {\n\t\t\tconst startSnapshot = dragStartNodes;\n\t\t\tdragStartNodes = null;\n\t\t\tif (erasedInDrag.size > 0 && options.history && startSnapshot) {\n\t\t\t\tconst erasedIds = new Set(Array.from(erasedInDrag.keys()));\n\n\t\t\t\toptions.history.execute({\n\t\t\t\t\tname: 'Erase strokes',\n\t\t\t\t\texecute: () => {\n\t\t\t\t\t\tconst remaining = options.getNodes().filter((n) => !erasedIds.has(n.id));\n\t\t\t\t\t\toptions.setNodes(remaining as TNode[]);\n\t\t\t\t\t},\n\t\t\t\t\tundo: () => {\n\t\t\t\t\t\toptions.setNodes(startSnapshot);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\terasedInDrag.clear();\n\t\t\treturn null;\n\t\t}\n\n\t\tif (!activeStroke) return null;\n\n\t\tconst stroke = activeStroke;\n\t\tactiveStroke = null;\n\t\toptions.onActiveStrokeChange?.(null);\n\n\t\tif (stroke.points.length === 0) return null;\n\n\t\tconst drawOpts = options.drawOptions ?? {};\n\t\tconst smoothed = streamlinePoints(stroke.points, drawOpts.streamline ?? 0.35);\n\t\tconst node = strokeToShapeNode(\n\t\t\t{ ...stroke, points: smoothed },\n\t\t\t{\n\t\t\t\ttolerance: drawOpts.tolerance ?? 1.0,\n\t\t\t\tcornerAngleDeg: drawOpts.cornerAngleDeg ?? 40\n\t\t\t}\n\t\t) as TNode;\n\n\t\taddStrokeNode(node, `Draw ${stroke.tool}`);\n\t\toptions.onStrokeComplete?.(node);\n\t\treturn node;\n\t}\n\n\tfunction addStrokeNode(node: TNode, name: string) {\n\t\tif (options.history) {\n\t\t\toptions.history.execute({\n\t\t\t\tname,\n\t\t\t\texecute: () => {\n\t\t\t\t\toptions.setNodes([...options.getNodes(), node]);\n\t\t\t\t},\n\t\t\t\tundo: () => {\n\t\t\t\t\toptions.setNodes(options.getNodes().filter((n) => n.id !== node.id) as TNode[]);\n\t\t\t\t}\n\t\t\t});\n\t\t} else {\n\t\t\toptions.setNodes([...options.getNodes(), node]);\n\t\t}\n\t}\n\n\t/**\n\t * Draws a whole stroke at once (for agents and code): validates the points, the brush and its\n\t * paint (an unknown or wet brush, or bad gradient paint, is refused before anything changes),\n\t * adds the stroke node as one undo step and returns its id and bounds. Brushed strokes load the paint engine first, so this is\n\t * async.\n\t */\n\tasync function drawStroke(input: DrawStrokeInput): Promise<DrawStrokeResult<TNode>> {\n\t\tconst points = input.points;\n\t\tif (!Array.isArray(points) || points.length === 0) throw new Error('drawStroke needs at least one point ([x, y] in canvas coordinates).');\n\t\tfor (const p of points) {\n\t\t\tif (!Array.isArray(p) || p.length < 2 || !Number.isFinite(p[0]) || !Number.isFinite(p[1])) {\n\t\t\t\tthrow new Error('Every point must be [x, y] with finite numbers (canvas coordinates).');\n\t\t\t}\n\t\t}\n\t\tconst tool = input.tool ?? 'pen';\n\t\tif (tool !== 'pen' && tool !== 'highlighter') throw new Error(`Unknown tool '${String(tool)}': use 'pen' or 'highlighter'.`);\n\t\tconst style: FreehandStrokeStyle = { ...createDefaultStrokeStyle(tool), ...input.style };\n\t\tif (!(style.width > 0)) throw new Error('width must be a positive number of canvas units.');\n\t\tconst profiles = { pressure: input.pressure, tiltX: input.tiltX, tiltY: input.tiltY };\n\t\tfor (const [key, values] of Object.entries(profiles)) {\n\t\t\tif (values === undefined) continue;\n\t\t\tconst [lo, hi] = key === 'pressure' ? [0, 1] : [-90, 90];\n\t\t\tif (!Array.isArray(values) || values.length === 0 || values.some((v) => !Number.isFinite(v) || v < lo || v > hi)) {\n\t\t\t\tthrow new Error(`${key} must be a non-empty list of numbers in ${lo}..${hi}.`);\n\t\t\t}\n\t\t}\n\t\tif ((input.pressure || input.tiltX || input.tiltY) && style.brush === undefined) {\n\t\t\tthrow new Error('pressure and tilt profiles only shape brushed strokes: pass a brush too (see canvas.listBrushes).');\n\t\t}\n\t\tif (style.paint !== undefined && style.brush === undefined) {\n\t\t\tthrow new Error('paint (gradient paint) only colours brushed strokes: pass a brush too (see canvas.listBrushes), or use color for a plain stroke.');\n\t\t}\n\t\tif (style.brush !== undefined) await validateCanvasBrush(style.brush as CanvasBrush);\n\t\t// Store the paint normalised (lowercase #rrggbb, defaults filled in) so the result shows what renders.\n\t\tif (style.paint !== undefined) style.paint = (await loadCanvasBrushstrokeRenderer()).resolveCanvasPaint(style.paint) as CanvasBrushPaint;\n\n\t\tconst id = options.generateNodeId?.() ?? `stroke-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;\n\t\tconst smoothed = input.streamline ? streamlinePoints(points, options.drawOptions?.streamline ?? 0.35) : [...points];\n\t\tconst node = strokeToShapeNode(\n\t\t\t{ id, tool, points: smoothed, style },\n\t\t\t{ tolerance: options.drawOptions?.tolerance ?? 1.0, cornerAngleDeg: options.drawOptions?.cornerAngleDeg ?? 40 }\n\t\t) as TNode;\n\t\tconst shape = (node.data as { shape: { brush?: Record<string, unknown> } }).shape;\n\t\tif (shape.brush) {\n\t\t\tfor (const [key, values] of Object.entries(profiles)) if (values) shape.brush[key] = [...values];\n\t\t}\n\t\taddStrokeNode(node, `Draw ${tool}`);\n\t\toptions.onStrokeComplete?.(node);\n\t\tconst pos = node.position ?? { x: 0, y: 0 };\n\t\treturn {\n\t\t\tid: node.id,\n\t\t\tnode,\n\t\t\tbounds: { x: pos.x, y: pos.y, width: Number(node.width ?? 0), height: Number(node.height ?? 0) },\n\t\t\t...(shape.brush ? { brush: shape.brush } : {})\n\t\t};\n\t}\n\n\tfunction cancelStroke() {\n\t\tif (activeStroke) {\n\t\t\tactiveStroke = null;\n\t\t\toptions.onActiveStrokeChange?.(null);\n\t\t}\n\t\tif (erasedInDrag.size > 0) {\n\t\t\t// Restore nodes erased during this aborted drag if any\n\t\t\tconst entries = Array.from(erasedInDrag.values());\n\t\t\tconst current = [...options.getNodes()];\n\t\t\tconst sorted = [...entries].sort((a, b) => a.index - b.index);\n\t\t\tfor (const { node, index } of sorted) {\n\t\t\t\tconst insertAt = Math.min(index, current.length);\n\t\t\t\tcurrent.splice(insertAt, 0, node);\n\t\t\t}\n\t\t\toptions.setNodes(current);\n\t\t\terasedInDrag.clear();\n\t\t}\n\t}\n\n\tfunction eraseAt(point: Point, radius = 12): TNode[] {\n\t\tconst nodes = options.getNodes();\n\t\tconst candidates = findStrokesAtPoint(point, nodes, radius);\n\t\tconst newlyErased: TNode[] = [];\n\n\t\tfor (const candidate of candidates) {\n\t\t\tif (erasedInDrag.has(candidate.id) || isLocked(candidate)) continue;\n\n\t\t\tconst index = nodes.findIndex((n) => n.id === candidate.id);\n\t\t\tif (index !== -1) {\n\t\t\t\terasedInDrag.set(candidate.id, { node: candidate as TNode, index });\n\t\t\t\tnewlyErased.push(candidate as TNode);\n\t\t\t}\n\t\t}\n\n\t\tif (newlyErased.length > 0) {\n\t\t\tconst erasedSet = new Set(newlyErased.map((n) => n.id));\n\t\t\tconst nextNodes = nodes.filter((n) => !erasedSet.has(n.id)) as TNode[];\n\t\t\toptions.setNodes(nextNodes);\n\t\t\toptions.onNodesErase?.(newlyErased);\n\t\t}\n\n\t\treturn newlyErased;\n\t}\n\n\treturn {\n\t\tget tool() {\n\t\t\treturn currentTool;\n\t\t},\n\t\tsetTool,\n\t\tget style() {\n\t\t\treturn currentStyle;\n\t\t},\n\t\tsetStyle,\n\t\tget activeStroke() {\n\t\t\treturn activeStroke;\n\t\t},\n\t\tget isDrawing() {\n\t\t\treturn activeStroke !== null;\n\t\t},\n\t\tstartStroke,\n\t\tcontinueStroke,\n\t\tendStroke,\n\t\tcancelStroke,\n\t\teraseAt,\n\t\tdrawStroke,\n\t\t/** The dry paint presets a stroke can be brushed with (loads the paint engine). */\n\t\tlistBrushes: listCanvasBrushes\n\t};\n}\n\nexport type CanvasDrawController = ReturnType<typeof createCanvasDrawController>;\n\n/** Modifier options when computing bounds while dragging/drawing. */\nexport interface CanvasDragBoundsModifiers {\n\t/** If true (e.g. Shift key held), constrain the bounding box to 1:1 aspect ratio. */\n\tconstrain?: boolean;\n\t/** If true (e.g. Alt/Option key held), draw/expand outward from the initial start point as center. */\n\tfromCenter?: boolean;\n}\n\nexport interface CanvasDragBounds {\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n}\n\n/**\n * Computes bounding rectangle from start and current points, supporting\n * `constrain` (aspect ratio 1:1, Shift key) and `fromCenter` (origin as center, Alt/Option key).\n */\nexport function computeDragBounds(\n\tstart: { x: number; y: number },\n\tcurrent: { x: number; y: number },\n\tmodifiers: CanvasDragBoundsModifiers = {}\n): CanvasDragBounds {\n\tlet dx = current.x - start.x;\n\tlet dy = current.y - start.y;\n\n\tif (modifiers.constrain) {\n\t\tconst absDx = Math.abs(dx);\n\t\tconst absDy = Math.abs(dy);\n\t\tconst maxDim = Math.max(absDx, absDy);\n\t\tdx = (dx < 0 ? -1 : 1) * maxDim;\n\t\tdy = (dy < 0 ? -1 : 1) * maxDim;\n\t}\n\n\tif (modifiers.fromCenter) {\n\t\tconst halfW = Math.abs(dx);\n\t\tconst halfH = Math.abs(dy);\n\t\treturn {\n\t\t\tx: start.x - halfW,\n\t\t\ty: start.y - halfH,\n\t\t\twidth: halfW * 2,\n\t\t\theight: halfH * 2\n\t\t};\n\t}\n\n\treturn {\n\t\tx: Math.min(start.x, start.x + dx),\n\t\ty: Math.min(start.y, start.y + dy),\n\t\twidth: Math.abs(dx),\n\t\theight: Math.abs(dy)\n\t};\n}\n\n/**\n * Computes bounding rectangle from start point and raw dimensions (dx, dy).\n */\nexport function computeDragBoundsFromPoints(\n\tstartX: number,\n\tstartY: number,\n\tcurrentX: number,\n\tcurrentY: number,\n\tmodifiers: CanvasDragBoundsModifiers = {}\n): CanvasDragBounds {\n\treturn computeDragBounds({ x: startX, y: startY }, { x: currentX, y: currentY }, modifiers);\n}\n","import dagre from \"@dagrejs/dagre\";\nimport type { DiagramFamily, DiagramGraph, DiagramMeta, GraphNode, Point } from \"../../diagram/types.js\";\nimport { getFamilyLayout } from \"../../diagram/layout-registry.js\";\n\n/**\n\n * Automatic layout of a Mermaid diagram graph with dagre, honouring nested groups and per-group\n * direction overrides. Pure functions — no React, no DOM.\n *\n * @module\n */\n\nexport interface DiagramSize {\n width: number;\n height: number;\n}\n\nexport interface DiagramLayoutOptions {\n /** Node size estimator; defaults to {@link estimateDiagramNodeSize}. */\n measure?: (node: GraphNode) => DiagramSize;\n /** Gap between nodes in a rank, in px (default 48). */\n nodeSep?: number;\n /** Gap between ranks, in px (default 72). */\n rankSep?: number;\n /** Padding inside groups around their children (default 24). */\n groupPadding?: number;\n /** Height reserved for a group's title (default 32). */\n groupTitleHeight?: number;\n /**\n * Diagram family. A family that registered its own layout (`registerFamilyLayout`:\n * pie, quadrant, sequence, Gantt…) is laid out from its data instead of with dagre.\n */\n family?: DiagramFamily;\n}\n\nexport const DEFAULT_DIAGRAM_NODE_WIDTH = 160;\nexport const DEFAULT_DIAGRAM_NODE_HEIGHT = 56;\n\n/** Rough text-metric size estimate (no DOM). Hosts can supply real measurements. */\nexport function estimateDiagramNodeSize(node: GraphNode): DiagramSize {\n const lines = String(node.label ?? node.id).split(/<br\\s*\\/?>|\\n/);\n const longest = Math.max(...lines.map((l) => l.replace(/<[^>]+>/g, \"\").length), 1);\n let width = Math.min(360, Math.max(DEFAULT_DIAGRAM_NODE_WIDTH, longest * 8 + 48));\n let height = Math.max(DEFAULT_DIAGRAM_NODE_HEIGHT, lines.length * 20 + 28);\n const extra = extraLines(node);\n if (extra) height += extra * 18 + 12;\n switch (node.shape) {\n case \"actor\":\n case \"c4-person\":\n height = Math.round(height * 1.5);\n break;\n case \"point\":\n width = Math.max(72, longest * 7 + 36);\n height = 30;\n break;\n case \"commit\": {\n const d = Math.max(56, Math.min(120, longest * 7 + 24));\n width = d;\n height = d;\n break;\n }\n case \"diam\":\n case \"hex\":\n case \"trap-b\":\n case \"trap-t\":\n case \"lean-r\":\n case \"lean-l\":\n width = Math.round(width * 1.25);\n height = Math.round(height * 1.4);\n break;\n case \"circle\":\n case \"dbl-circ\":\n case \"sm-circ\":\n case \"fr-circ\":\n case \"f-circ\":\n case \"cross-circ\": {\n const d = Math.max(width * 0.75, height * 1.3);\n width = Math.round(d);\n height = Math.round(d);\n break;\n }\n case \"stateStart\":\n case \"stateEnd\":\n width = 28;\n height = 28;\n break;\n case \"fork\":\n case \"join\":\n width = 120;\n height = 10;\n break;\n case \"choice\":\n width = 48;\n height = 48;\n break;\n case \"cyl\":\n case \"h-cyl\":\n case \"lin-cyl\":\n height = Math.round(height * 1.3);\n break;\n case \"image\": {\n // The picture at its w × h (Mermaid's keys), with the label above or below it.\n const picture = diagramImageSize(node);\n const label = String(node.label ?? \"\").trim() ? IMAGE_LABEL_HEIGHT : 0;\n width = Math.max(picture.width, label ? Math.min(360, longest * 7 + 16) : 0);\n height = picture.height + label;\n break;\n }\n default:\n break;\n }\n return { width, height };\n}\n\n/** Height of an image node's label line. */\nconst IMAGE_LABEL_HEIGHT = 24;\n\n/** An image node's picture size: Mermaid's `w` / `h`, else 96 square (or `w` square). */\nexport function diagramImageSize(node: GraphNode): DiagramSize {\n const num = (v: unknown) => (typeof v === \"number\" && v > 0 ? v : undefined);\n const w = num(node.data?.w);\n const h = num(node.data?.h);\n return { width: w ?? h ?? 96, height: h ?? w ?? 96 };\n}\n\nfunction extraLines(node: GraphNode): number {\n const data = node.data ?? {};\n const members = Array.isArray(data.members) ? data.members.length : 0;\n const attributes = Array.isArray(data.attributes) ? data.attributes.length : 0;\n const descriptions = Array.isArray(data.descriptions) ? data.descriptions.length : 0;\n const details = Array.isArray(data.details) ? data.details.length : 0;\n return members + attributes + descriptions + details;\n}\n\nexport interface DiagramLayout {\n /** Top-left positions in absolute canvas coordinates. */\n positions: Record<string, Point>;\n /** Sizes for nodes and groups. */\n sizes: Record<string, DiagramSize>;\n}\n\nfunction rankdir(direction: string): \"TB\" | \"BT\" | \"LR\" | \"RL\" {\n const d = direction.toUpperCase();\n if (d === \"TD\") return \"TB\";\n if (d === \"TB\" || d === \"BT\" || d === \"LR\" || d === \"RL\") return d;\n return \"TB\";\n}\n\n/**\n * Lays out a graph. Nested groups are laid out recursively: each group's\n * children are arranged with the group's own direction, the group then takes\n * part in its parent's layout as a single box.\n */\nexport function autoLayoutDiagram(graph: DiagramGraph, options: DiagramLayoutOptions = {}): DiagramLayout {\n const measure = options.measure ?? estimateDiagramNodeSize;\n const custom = getFamilyLayout(options.family);\n if (custom) return custom(graph, { v: 1 }, { ...options, measure });\n const nodeSep = options.nodeSep ?? 48;\n const rankSep = options.rankSep ?? 72;\n const pad = options.groupPadding ?? 24;\n const titleH = options.groupTitleHeight ?? 32;\n\n const childrenOf = new Map<string | undefined, { id: string; isGroup: boolean }[]>();\n for (const n of graph.nodes) push(childrenOf, n.parentId, { id: n.id, isGroup: false });\n for (const g of graph.groups) push(childrenOf, g.parentId, { id: g.id, isGroup: true });\n const nodeById = new Map(graph.nodes.map((n) => [n.id, n]));\n const groupById = new Map(graph.groups.map((g) => [g.id, g]));\n\n const sizes: Record<string, DiagramSize> = {};\n const relative: Record<string, Point> = {}; // position relative to parent\n\n const layoutScope = (scope: string | undefined, direction: string): DiagramSize => {\n const children = childrenOf.get(scope) ?? [];\n // Lay out groups first so their size is known.\n for (const c of children) {\n if (c.isGroup) {\n const g = groupById.get(c.id)!;\n const inner = layoutScope(c.id, g.direction ?? direction);\n sizes[c.id] = { width: inner.width + pad * 2, height: inner.height + pad * 2 + titleH };\n } else {\n sizes[c.id] = measure(nodeById.get(c.id)!);\n }\n }\n if (!children.length) return { width: 120, height: 40 };\n\n const g = new dagre.graphlib.Graph({ multigraph: true });\n g.setGraph({\n rankdir: rankdir(direction),\n nodesep: nodeSep,\n ranksep: rankSep,\n marginx: 0,\n marginy: 0,\n });\n g.setDefaultEdgeLabel(() => ({}));\n const ids = new Set(children.map((c) => c.id));\n for (const c of children)\n g.setNode(c.id, { width: sizes[c.id]!.width, height: sizes[c.id]!.height });\n\n // Edges between members of this scope (lifting endpoints to the member that contains them).\n const lift = (id: string): string | null => {\n let cur: string | undefined = id;\n const seen = new Set<string>();\n while (cur && !seen.has(cur)) {\n if (ids.has(cur)) return cur;\n seen.add(cur);\n cur = nodeById.get(cur)?.parentId ?? groupById.get(cur)?.parentId;\n }\n return null;\n };\n graph.edges.forEach((e, i) => {\n const s = lift(e.source);\n const t = lift(e.target);\n if (s && t && s !== t) g.setEdge(s, t, { minlen: Math.max(1, e.length) }, `e${i}`);\n });\n dagre.layout(g);\n\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n for (const c of children) {\n const n = g.node(c.id);\n const w = sizes[c.id]!.width;\n const h = sizes[c.id]!.height;\n const x = n.x - w / 2;\n const y = n.y - h / 2;\n relative[c.id] = [x, y];\n minX = Math.min(minX, x);\n minY = Math.min(minY, y);\n maxX = Math.max(maxX, x + w);\n maxY = Math.max(maxY, y + h);\n }\n // Normalise so the scope's content starts at (0,0).\n for (const c of children) {\n const [x, y] = relative[c.id]!;\n relative[c.id] = [x - minX, y - minY];\n }\n return { width: maxX - minX, height: maxY - minY };\n };\n\n layoutScope(undefined, graph.direction);\n\n // Convert relative → absolute.\n const positions: Record<string, Point> = {};\n const absolute = (id: string): Point => {\n if (positions[id]) return positions[id]!;\n const parentId = nodeById.get(id)?.parentId ?? groupById.get(id)?.parentId;\n const [rx, ry] = relative[id] ?? [0, 0];\n if (!parentId) {\n positions[id] = [rx, ry];\n } else {\n const [px, py] = absolute(parentId);\n positions[id] = [px + pad + rx, py + pad + titleH + ry];\n }\n return positions[id]!;\n };\n for (const n of graph.nodes) absolute(n.id);\n for (const g of graph.groups) absolute(g.id);\n\n return { positions, sizes };\n}\n\nfunction push<K, V>(map: Map<K, V[]>, key: K, value: V): void {\n const arr = map.get(key);\n if (arr) arr.push(value);\n else map.set(key, [value]);\n}\n\n/**\n * Returns positions for every node/group: stored metadata where available,\n * auto-layout for the rest. New nodes are placed near their neighbours so a\n * partially laid-out diagram does not jump.\n */\nexport function ensureDiagramLayout(\n graph: DiagramGraph,\n meta: DiagramMeta,\n options?: DiagramLayoutOptions,\n): DiagramLayout {\n const measure = options?.measure ?? estimateDiagramNodeSize;\n const custom = getFamilyLayout(options?.family);\n if (custom) return custom(graph, meta, { ...(options ?? {}), measure });\n const stored = meta.layout ?? {};\n const allIds = [...graph.nodes.map((n) => n.id), ...graph.groups.map((g) => g.id)];\n const missing = allIds.filter((id) => !stored[id]);\n const sizes: Record<string, DiagramSize> = {};\n for (const n of graph.nodes)\n sizes[n.id] = meta.sizes?.[n.id]\n ? { width: meta.sizes[n.id]![0], height: meta.sizes[n.id]![1] }\n : measure(n);\n\n if (!missing.length) {\n const positions: Record<string, Point> = {};\n for (const id of allIds) positions[id] = stored[id]!;\n for (const g of graph.groups) sizes[g.id] = diagramGroupSize(g.id, graph, positions, sizes, meta);\n return { positions, sizes };\n }\n\n const auto = autoLayoutDiagram(graph, options);\n if (missing.length === allIds.length) return auto;\n\n // Blend: keep stored positions, place missing ones relative to auto layout\n // by aligning on the centroid of the nodes that have both.\n const both = allIds.filter((id) => stored[id] && auto.positions[id]);\n let dx = 0;\n let dy = 0;\n if (both.length) {\n for (const id of both) {\n dx += stored[id]![0] - auto.positions[id]![0];\n dy += stored[id]![1] - auto.positions[id]![1];\n }\n dx /= both.length;\n dy /= both.length;\n }\n const positions: Record<string, Point> = {};\n for (const id of allIds) {\n positions[id] = stored[id] ?? [auto.positions[id]![0] + dx, auto.positions[id]![1] + dy];\n }\n for (const g of graph.groups) sizes[g.id] = diagramGroupSize(g.id, graph, positions, sizes, meta);\n return { positions, sizes };\n}\n\n/** Bounding box of a group's descendants, with padding. */\nexport function diagramGroupSize(\n groupId: string,\n graph: DiagramGraph,\n positions: Record<string, Point>,\n sizes: Record<string, DiagramSize>,\n meta: DiagramMeta,\n pad = 24,\n titleH = 32,\n): DiagramSize {\n if (meta.sizes?.[groupId])\n return { width: meta.sizes[groupId]![0], height: meta.sizes[groupId]![1] };\n const members = [\n ...graph.nodes.filter((n) => n.parentId === groupId).map((n) => n.id),\n ...graph.groups.filter((g) => g.parentId === groupId).map((g) => g.id),\n ];\n if (!members.length) return { width: 200, height: 100 };\n const [gx, gy] = positions[groupId] ?? [0, 0];\n let maxX = 0;\n let maxY = 0;\n for (const id of members) {\n const size =\n sizes[id] ??\n (graph.groups.some((g) => g.id === id)\n ? diagramGroupSize(id, graph, positions, sizes, meta, pad, titleH)\n : { width: DEFAULT_DIAGRAM_NODE_WIDTH, height: DEFAULT_DIAGRAM_NODE_HEIGHT });\n sizes[id] = size;\n const [x, y] = positions[id] ?? [gx, gy];\n maxX = Math.max(maxX, x - gx + size.width);\n maxY = Math.max(maxY, y - gy + size.height);\n }\n return { width: maxX + pad, height: maxY + pad };\n}\n","import type {\n ArrowHead,\n DiagramGraph,\n EdgePattern,\n GraphEdge,\n GraphGroup,\n GraphNode,\n Point,\n} from \"../../diagram/types.js\";\nimport type { DiagramLayout, DiagramSize } from \"./layout.js\";\n\n/**\n * Projection of a Mermaid diagram graph onto canvas (React Flow / Svelte Flow) nodes and edges.\n * Kept free of React so it can be unit-tested and reused (e.g. for export).\n *\n * @module\n */\n\nexport interface MermaidNodeData extends Record<string, unknown> {\n node: GraphNode;\n family: string;\n /** Resolved `classDef` styles for the node's classes, merged with inline styles. */\n resolvedStyles: Record<string, string>;\n editable: boolean;\n}\n\nexport interface MermaidGroupData extends Record<string, unknown> {\n group: GraphGroup;\n family: string;\n resolvedStyles: Record<string, string>;\n editable: boolean;\n}\n\nexport interface MermaidEdgeData extends Record<string, unknown> {\n edge: GraphEdge;\n family: string;\n resolvedStyles: Record<string, string>;\n editable: boolean;\n}\n\nexport interface MermaidFlowNode {\n id: string;\n /** `mermaid-node` / `mermaid-group`, or a family canvas node type. */\n type: string;\n position: { x: number; y: number };\n data: MermaidNodeData | MermaidGroupData | Record<string, unknown>;\n parentId?: string;\n width: number;\n height: number;\n style?: Record<string, string | number>;\n draggable: boolean;\n selectable: boolean;\n connectable: boolean;\n zIndex: number;\n extent?: \"parent\";\n className?: string;\n}\n\nexport interface MermaidFlowEdge {\n id: string;\n /** `mermaid-edge`, or a family canvas edge type. */\n type: string;\n source: string;\n target: string;\n data: MermaidEdgeData;\n label?: string;\n selectable: boolean;\n reconnectable: boolean;\n zIndex: number;\n sourceHandle?: string;\n targetHandle?: string;\n className?: string;\n}\n\n/** Parses `fill:#f9f,stroke:#333,stroke-width:4px` into an object. */\nexport function parseMermaidStyles(styles: string[]): Record<string, string> {\n const out: Record<string, string> = {};\n for (const s of styles) {\n const idx = s.indexOf(\":\");\n if (idx === -1) continue;\n out[s.slice(0, idx).trim()] = s.slice(idx + 1).trim();\n }\n return out;\n}\n\nexport function resolveMermaidStyles(\n classes: string[],\n inline: string[],\n classDefs: Record<string, string[]>,\n): Record<string, string> {\n let out: Record<string, string> = {};\n if (classDefs.default) out = { ...out, ...parseMermaidStyles(classDefs.default) };\n for (const cls of classes)\n if (classDefs[cls]) out = { ...out, ...parseMermaidStyles(classDefs[cls]!) };\n return { ...out, ...parseMermaidStyles(inline) };\n}\n\nfunction depth(id: string, parents: Map<string, string | undefined>): number {\n let d = 0;\n let cur = parents.get(id);\n const seen = new Set<string>();\n while (cur && !seen.has(cur)) {\n seen.add(cur);\n d++;\n cur = parents.get(cur);\n }\n return d;\n}\n\nexport interface MermaidFlowOptions {\n editable?: boolean;\n /** Override positions (e.g. during a drag). Absolute coordinates. */\n positions?: Record<string, Point>;\n sizes?: Record<string, DiagramSize>;\n}\n\n/**\n * Converts the graph + layout into React Flow nodes. Positions in the layout\n * are absolute; React Flow wants child positions relative to the parent, so\n * they are converted here.\n */\nexport function mermaidFlowNodes(\n graph: DiagramGraph,\n layout: DiagramLayout,\n options: MermaidFlowOptions = {},\n): MermaidFlowNode[] {\n const editable = options.editable ?? false;\n const positions = { ...layout.positions, ...(options.positions ?? {}) };\n const sizes = { ...layout.sizes, ...(options.sizes ?? {}) };\n const parents = new Map<string, string | undefined>();\n for (const n of graph.nodes) parents.set(n.id, n.parentId);\n for (const g of graph.groups) parents.set(g.id, g.parentId);\n\n const rel = (id: string, parentId: string | undefined): { x: number; y: number } => {\n const [x, y] = positions[id] ?? [0, 0];\n if (!parentId) return { x, y };\n const [px, py] = positions[parentId] ?? [0, 0];\n return { x: x - px, y: y - py };\n };\n\n const groups: MermaidFlowNode[] = graph.groups.map((g) => {\n const size = sizes[g.id] ?? { width: 240, height: 160 };\n return {\n id: g.id,\n type: \"mermaid-group\",\n position: rel(g.id, g.parentId),\n parentId: g.parentId,\n data: {\n group: g,\n family: \"\",\n resolvedStyles: resolveMermaidStyles(g.classes, g.styles, graph.classDefs),\n editable,\n },\n width: size.width,\n height: size.height,\n style: { width: size.width, height: size.height },\n draggable: editable && !g.data?.locked,\n selectable: editable,\n connectable: editable && !g.data?.locked,\n zIndex: depth(g.id, parents),\n };\n });\n\n const nodes: MermaidFlowNode[] = graph.nodes.map((n) => {\n const size = sizes[n.id] ?? { width: 160, height: 56 };\n return {\n id: n.id,\n type: \"mermaid-node\",\n position: rel(n.id, n.parentId),\n parentId: n.parentId,\n data: {\n node: n,\n family: \"\",\n resolvedStyles: resolveMermaidStyles(n.classes, n.styles, graph.classDefs),\n editable,\n },\n width: size.width,\n height: size.height,\n style: { width: size.width, height: size.height },\n draggable: editable && !n.data?.locked,\n selectable: editable,\n connectable: editable && !n.data?.locked && !n.data?.synthetic,\n zIndex: depth(n.id, parents) + 100,\n };\n });\n\n // Parents must precede children for React Flow.\n const order = (a: MermaidFlowNode, b: MermaidFlowNode) => depth(a.id, parents) - depth(b.id, parents);\n return [...groups.sort(order), ...nodes.sort(order)];\n}\n\nexport function mermaidFlowEdges(graph: DiagramGraph, options: MermaidFlowOptions = {}): MermaidFlowEdge[] {\n const editable = options.editable ?? false;\n return graph.edges.map((e) => ({\n id: e.id,\n type: \"mermaid-edge\",\n source: e.source,\n target: e.target,\n label: e.label,\n data: {\n edge: e,\n family: \"\",\n resolvedStyles: resolveMermaidStyles(e.classes, e.styles, graph.classDefs),\n editable,\n },\n selectable: editable,\n reconnectable: editable,\n zIndex: 1000,\n }));\n}\n\n/** SVG marker id for an arrow head (markers are defined once by the canvas). */\nexport function mermaidMarkerId(head: ArrowHead, side: \"start\" | \"end\"): string | undefined {\n if (head === \"none\") return undefined;\n return `pixerate-mermaid-marker-${head}-${side}`;\n}\n\nexport function mermaidEdgeDash(pattern: EdgePattern): string | undefined {\n switch (pattern) {\n case \"dotted\":\n return \"6 4\";\n case \"invisible\":\n return \"0 1\";\n default:\n return undefined;\n }\n}\n\nexport function mermaidEdgeWidth(pattern: EdgePattern): number {\n return pattern === \"thick\" ? 3.5 : 1.75;\n}\n","import type { DiagramGraph, Point } from \"../../diagram/types.js\";\nimport { DEFAULT_DIAGRAM_NODE_HEIGHT, type DiagramLayout, type DiagramSize } from \"./layout.js\";\n\n/**\n * Helpers for showing a laid-out diagram outside the editor: sizing an inline\n * viewer to its content, animating between two layouts, and trimming a graph\n * that is still being streamed in. Pure functions — no React, no DOM.\n *\n * @module\n */\n\nexport interface DiagramBounds {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/** Extent of every positioned node and group, or `null` for an empty layout. */\nexport function diagramLayoutBounds(layout: DiagramLayout): DiagramBounds | null {\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n for (const [id, [x, y]] of Object.entries(layout.positions)) {\n const size = layout.sizes[id] ?? { width: 0, height: 0 };\n minX = Math.min(minX, x);\n minY = Math.min(minY, y);\n maxX = Math.max(maxX, x + size.width);\n maxY = Math.max(maxY, y + size.height);\n }\n if (minX === Infinity) return null;\n return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n}\n\nexport interface DiagramCanvasHeightOptions {\n /** Smallest height returned (default 320). */\n min?: number;\n /** Largest height returned; taller content is zoomed out (default 560). */\n max?: number;\n /** Room added around the content (default 48). */\n padding?: number;\n}\n\nfunction clampHeight(content: number, options: DiagramCanvasHeightOptions = {}): number {\n const { min = 320, max = 560, padding = 48 } = options;\n return Math.round(Math.min(max, Math.max(min, content + padding)));\n}\n\n/** Container height that shows a layout at 1:1 where it fits, clamped. */\nexport function diagramCanvasHeight(layout: DiagramLayout, options?: DiagramCanvasHeightOptions): number {\n return clampHeight(diagramLayoutBounds(layout)?.height ?? 0, options);\n}\n\n// Shared with dagre's default rank separation, so the estimate and the real\n// height agree for a simple chain.\nconst RANK_HEIGHT = DEFAULT_DIAGRAM_NODE_HEIGHT + 72;\n// Counts alone cannot tell a chain from a fan-out; both clamp long before the\n// error matters.\nconst NODES_PER_RANK = 2;\nconst GROUP_BAND = 24 * 2 + 32;\n\n/**\n * Height to reserve before the asynchronous layout resolves, from the graph's\n * counts alone. Uses the same clamp as {@link diagramCanvasHeight}, so the container\n * does not jump when the layout lands.\n */\nexport function estimateDiagramCanvasHeight(graph: DiagramGraph, options?: DiagramCanvasHeightOptions): number {\n const ranks = Math.ceil(graph.nodes.length / NODES_PER_RANK);\n return clampHeight(ranks * RANK_HEIGHT + graph.groups.length * GROUP_BAND, options);\n}\n\nfunction mix(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\n\n/**\n * A layout partway between two others, for animating a re-layout: ids in both\n * interpolate at `t` (0 = `from`, 1 = `to`), ids new in `to` start at their\n * target, and ids missing from `to` drop out.\n */\nexport function blendDiagramLayout(from: DiagramLayout, to: DiagramLayout, t: number): DiagramLayout {\n if (t >= 1) return to;\n const positions: Record<string, Point> = {};\n for (const [id, [x, y]] of Object.entries(to.positions)) {\n const origin = from.positions[id];\n positions[id] = origin ? [mix(origin[0], x, t), mix(origin[1], y, t)] : [x, y];\n }\n const sizes: Record<string, DiagramSize> = {};\n for (const [id, size] of Object.entries(to.sizes)) {\n const origin = from.sizes[id];\n sizes[id] = origin\n ? { width: mix(origin.width, size.width, t), height: mix(origin.height, size.height, t) }\n : size;\n }\n return { positions, sizes };\n}\n\n/**\n * The graph without nodes that no edge touches yet. While a diagram streams\n * in, a node usually arrives a line before the edge that places it; showing it\n * alone makes the layout jump twice. Groups are kept when they (or a nested\n * group) still contain a node.\n */\nexport function connectedDiagramOnly(graph: DiagramGraph): DiagramGraph {\n const linked = new Set<string>();\n for (const edge of graph.edges) {\n linked.add(edge.source);\n linked.add(edge.target);\n }\n const nodes = graph.nodes.filter((n) => linked.has(n.id));\n if (nodes.length === graph.nodes.length) return graph;\n const parentOf = new Map(graph.groups.map((g) => [g.id, g.parentId]));\n const keep = new Set<string>();\n for (const node of nodes) {\n let id = node.parentId;\n while (id && !keep.has(id)) {\n keep.add(id);\n id = parentOf.get(id);\n }\n }\n // An edge can name a group as an endpoint (flowchart allows it).\n for (const id of linked) if (parentOf.has(id)) keep.add(id);\n return { ...graph, nodes, groups: graph.groups.filter((g) => keep.has(g.id)) };\n}\n","// Follows Ursula's MermaidStreamingView: nodes are held back until an edge places them,\n// layouts are recomputed only when the structure changes, and the view tweens between\n// layouts with `blendDiagramLayout`.\nimport { parseMermaid } from '../../diagram/document.js';\nimport type { DiagramGraph, MermaidDocument } from '../../diagram/types.js';\nimport { ensureDiagramLayout, type DiagramLayout, type DiagramLayoutOptions } from './layout.js';\nimport { connectedDiagramOnly } from './view.js';\n\nexport interface DiagramStreamResult {\n\t/** The diagram as far as it has arrived (its graph trimmed while incomplete). */\n\tdocument: MermaidDocument;\n\t/** Layout for `document.graph`, or `null` before any node can be placed. */\n\tlayout: DiagramLayout | null;\n\t/** True when the layout differs from the previous result (start a tween). */\n\trelaid: boolean;\n}\n\n/** Structure only: labels can change without moving anything. */\nfunction fingerprint(graph: DiagramGraph): string {\n\treturn JSON.stringify([\n\t\tgraph.direction,\n\t\tgraph.nodes.map((n) => [n.id, n.parentId ?? '', n.shape, String(n.label).length > 24 ? 1 : 0]),\n\t\tgraph.edges.map((e) => [e.source, e.target]),\n\t\tgraph.groups.map((g) => [g.id, g.parentId ?? '', g.direction ?? ''])\n\t]);\n}\n\n/**\n * Turns Mermaid text that is still being written (e.g. streamed from a model) into a\n * diagram and a layout to draw. While incomplete, nodes no edge touches yet are held\n * back (a node usually arrives a line before the edge that places it, and showing it\n * alone makes the layout jump twice). Layouts are only recomputed when nodes, edges or\n * groups change, so label text streaming in does not move anything.\n */\nexport class DiagramStream {\n\tprivate lastKey = '';\n\tprivate layout: DiagramLayout | null = null;\n\n\tconstructor(private readonly options: DiagramLayoutOptions = {}) {}\n\n\t/** Reads the source so far. `complete` when nothing more will arrive. */\n\tupdate(source: string, complete = false): DiagramStreamResult {\n\t\tconst text = streamedDiagramText(source);\n\t\tconst parsed = parseMermaid(text);\n\t\tconst document = !complete ? { ...parsed, graph: connectedDiagramOnly(parsed.graph) } : parsed;\n\t\tif (document.tier !== 'editable' || !document.graph.nodes.length) return { document, layout: this.layout && complete ? null : this.layout, relaid: false };\n\t\tconst key = fingerprint(document.graph);\n\t\tif (key === this.lastKey && this.layout) return { document, layout: this.layout, relaid: false };\n\t\tthis.lastKey = key;\n\t\tthis.layout = ensureDiagramLayout(document.graph, document.meta, { family: document.family, ...this.options });\n\t\treturn { document, layout: this.layout, relaid: true };\n\t}\n\n\t/** Forgets the previous layout (a new source). */\n\treset(): void {\n\t\tthis.lastKey = '';\n\t\tthis.layout = null;\n\t}\n}\n\n/**\n * The diagram text in a reply that is still arriving: the body of the first ```mermaid\n * (or unmarked) fence, even before it is closed, or the whole text when there is no fence.\n * Prose before the fence (a model's \"Here is the plan:\") is skipped.\n */\nexport function streamedDiagramText(source: string): string {\n\tconst open = /(?:^|\\n)[ ]{0,3}(`{3,}|~{3,})[ \\t]*(mermaid|mmd)?[^\\n`]*(?:\\n|$)/i.exec(source);\n\tif (!open) return source;\n\tconst rest = source.slice(open.index + open[0].length);\n\tconst fence = open[1]!;\n\tconst close = new RegExp(`(?:^|\\\\n)[ ]{0,3}${fence[0] === '`' ? '`' : '~'}{${fence.length},}[ \\\\t]*(?:\\\\n|$)`).exec(rest);\n\treturn close ? rest.slice(0, close.index) : rest.replace(/\\n[ ]{0,3}[`~]{1,2}$/, '');\n}\n\n/** Ease-out cubic, for layout tweens. */\nexport const easeDiagramTween = (t: number) => 1 - Math.pow(1 - Math.max(0, Math.min(1, t)), 3);\n","/** Concrete colours (no CSS variables), so the SVG draws the same as a file, an `<img>` or a PNG. */\nexport interface DiagramSvgTheme {\n\tbackground: string;\n\tnodeFill: string;\n\tnodeStroke: string;\n\ttext: string;\n\tedge: string;\n\tedgeLabelBackground: string;\n\tgroupFill: string;\n\tgroupStroke: string;\n\tgroupText: string;\n\tfontFamily: string;\n\tfontSize: number;\n}\n\nexport const DIAGRAM_SVG_LIGHT_THEME: DiagramSvgTheme = {\n\tbackground: '#ffffff',\n\tnodeFill: '#ffffff',\n\tnodeStroke: '#0f172a',\n\ttext: '#0f172a',\n\tedge: '#64748b',\n\tedgeLabelBackground: '#ffffff',\n\tgroupFill: '#f8fafc',\n\tgroupStroke: '#cbd5e1',\n\tgroupText: '#475569',\n\tfontFamily: 'system-ui, -apple-system, \"Segoe UI\", sans-serif',\n\tfontSize: 13\n};\n\nexport const DIAGRAM_SVG_DARK_THEME: DiagramSvgTheme = {\n\tbackground: '#0f172a',\n\tnodeFill: '#1e293b',\n\tnodeStroke: '#e2e8f0',\n\ttext: '#f1f5f9',\n\tedge: '#94a3b8',\n\tedgeLabelBackground: '#0f172a',\n\tgroupFill: '#111c33',\n\tgroupStroke: '#334155',\n\tgroupText: '#94a3b8',\n\tfontFamily: 'system-ui, -apple-system, \"Segoe UI\", sans-serif',\n\tfontSize: 13\n};\n","// Family drawings follow Ursula's family canvases (src/react/canvases/*.tsx and\n// src/react/canvas/familyCanvas.ts), drawn as framework-free marks so React, Svelte and the\n// SVG export share one renderer per family.\nimport type { DiagramFamily, DiagramGraph } from '../../diagram/types.js';\nimport type { DiagramLayout } from './layout.js';\nimport { DIAGRAM_SVG_DARK_THEME, DIAGRAM_SVG_LIGHT_THEME, type DiagramSvgTheme } from './theme.js';\n\n/**\n * Colours for family drawings. Concrete values draw the same everywhere (files, PNG); a\n * renderer may pass CSS (`var(--x, #fff)`) since the React and Svelte scenes paint with\n * `style`. `palette` colours slices, series, sections and branches in order.\n */\nexport interface DiagramSceneTheme extends DiagramSvgTheme {\n\t/** Categorical colours (pie slices, sections, branches, curves…), used in order. */\n\tpalette: string[];\n\t/** Text drawn on palette colours. */\n\tpaletteText: string;\n\t/** Axis lines, grid and lifelines. */\n\tgrid: string;\n\t/** Secondary text (axis labels, legends, notes). */\n\tmutedText: string;\n\t/** Selection outline. */\n\tselection: string;\n}\n\nconst PALETTE_LIGHT = ['#4e79a7', '#f28e2b', '#59a14f', '#e15759', '#76b7b2', '#edc948', '#b07aa1', '#ff9da7', '#9c755f', '#bab0ac', '#8cd17d', '#86bcb6'];\nconst PALETTE_DARK = ['#7aa6d6', '#ffad5c', '#7fcf73', '#ff8587', '#9fdcd6', '#ffe07a', '#d4a2c6', '#ffc2c9', '#c49a82', '#d9d0cc', '#b0eba3', '#acd8d3'];\n\nexport const DIAGRAM_SCENE_LIGHT_THEME: DiagramSceneTheme = {\n\t...DIAGRAM_SVG_LIGHT_THEME,\n\tpalette: PALETTE_LIGHT,\n\tpaletteText: '#0f172a',\n\tgrid: '#cbd5e1',\n\tmutedText: '#64748b',\n\tselection: '#2563eb'\n};\n\nexport const DIAGRAM_SCENE_DARK_THEME: DiagramSceneTheme = {\n\t...DIAGRAM_SVG_DARK_THEME,\n\tpalette: PALETTE_DARK,\n\tpaletteText: '#0f172a',\n\tgrid: '#334155',\n\tmutedText: '#94a3b8',\n\tselection: '#60a5fa'\n};\n\ninterface DiagramMarkBase {\n\t/** Unique within the scene (a stable key for frameworks). */\n\tkey: string;\n\t/** What the mark draws, for styling hooks (`slice`, `legend`, `axis`, `lifeline`, `bar`…). */\n\trole: string;\n\t/** The node, edge or group id clicking this mark selects (none: decoration). */\n\ttarget?: string;\n\t/** Mark belongs to the current selection. */\n\tselected?: boolean;\n\topacity?: number;\n}\n\nexport interface DiagramRectMark extends DiagramMarkBase {\n\tkind: 'rect';\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n\tfill: string;\n\tstroke?: string;\n\tstrokeWidth?: number;\n\tradius?: number;\n\tdash?: string;\n}\n\nexport interface DiagramPathMark extends DiagramMarkBase {\n\tkind: 'path';\n\td: string;\n\tfill?: string;\n\tstroke?: string;\n\tstrokeWidth?: number;\n\tdash?: string;\n\t/** Arrow head at the end (`arrow` open, `arrowclosed` filled). */\n\tmarkerEnd?: 'arrow' | 'arrowclosed';\n\tmarkerStart?: 'arrow' | 'arrowclosed';\n\t/** Bounds of the path; required when `d` uses arcs or relative commands. */\n\tbox?: { x: number; y: number; width: number; height: number };\n}\n\nexport interface DiagramCircleMark extends DiagramMarkBase {\n\tkind: 'circle';\n\tcx: number;\n\tcy: number;\n\tr: number;\n\tfill: string;\n\tstroke?: string;\n\tstrokeWidth?: number;\n}\n\nexport interface DiagramLineMark extends DiagramMarkBase {\n\tkind: 'line';\n\tx1: number;\n\ty1: number;\n\tx2: number;\n\ty2: number;\n\tstroke: string;\n\tstrokeWidth?: number;\n\tdash?: string;\n\tmarkerEnd?: 'arrow' | 'arrowclosed';\n\tmarkerStart?: 'arrow' | 'arrowclosed';\n}\n\nexport interface DiagramTextMark extends DiagramMarkBase {\n\tkind: 'text';\n\tx: number;\n\ty: number;\n\ttext: string;\n\tanchor: 'start' | 'middle' | 'end';\n\t/** Vertical alignment of the text to `y`. */\n\tbaseline: 'top' | 'middle' | 'bottom';\n\tsize: number;\n\tweight?: number;\n\tfill: string;\n\t/** Degrees, around (x, y). */\n\trotate?: number;\n\titalic?: boolean;\n}\n\nexport type DiagramMark = DiagramRectMark | DiagramPathMark | DiagramCircleMark | DiagramLineMark | DiagramTextMark;\n\n/** A family's drawing in canvas coordinates (the same as the layout's positions). */\nexport interface DiagramScene {\n\tfamily: DiagramFamily;\n\t/** Bounds of everything drawn. */\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n\t/** Draw in order. */\n\tmarks: DiagramMark[];\n}\n\nexport interface DiagramSceneOptions {\n\ttheme?: Partial<DiagramSceneTheme>;\n\t/** Selected node, edge or group ids (their marks get `selected`). */\n\tselection?: readonly string[];\n}\n\nexport interface FamilySceneInput {\n\tgraph: DiagramGraph;\n\tlayout: DiagramLayout;\n\ttheme: DiagramSceneTheme;\n\tselection: ReadonlySet<string>;\n}\n\n/** Draws a family from its graph and layout; returns marks in canvas coordinates. */\nexport type FamilyScene = (input: FamilySceneInput) => DiagramMark[];\n\n/**\n * `target` of the diagram title's marks: double-clicking it renames the title (`setTitle`)\n * rather than a node.\n */\nexport const DIAGRAM_TITLE_TARGET = '::title';\n\n/** The palette colour for index `i` (cycling). */\nexport function sceneColor(theme: DiagramSceneTheme, i: number): string {\n\treturn theme.palette[((i % theme.palette.length) + theme.palette.length) % theme.palette.length]!;\n}\n\n/** Rough text width (no DOM), in px. */\nexport function estimateTextWidth(text: string, size: number): number {\n\treturn text.length * size * 0.58;\n}\n\n","import { PIE_METRICS, pieGeometry } from '../../../diagram/pie/geometry.js';\nimport { DIAGRAM_TITLE_TARGET, sceneColor, type DiagramMark, type FamilyScene } from '../scene-kit.js';\n\nconst M = PIE_METRICS;\n\n/**\n * Pie charts as Mermaid draws them (Ursula's `canvases/pie.tsx`): wedges in document order\n * with a percentage on each, the outer circle, the legend to the right and the title above.\n * A wedge and its legend row select the slice.\n */\nexport const pieScene: FamilyScene = ({ graph, theme }) => {\n\tconst geo = pieGeometry(graph);\n\tconst marks: DiagramMark[] = [];\n\tfor (const slice of geo.slices) {\n\t\tif (!slice.drawn) continue;\n\t\tconst r = geo.radius;\n\t\tmarks.push({\n\t\t\tkind: 'path',\n\t\t\tkey: `wedge:${slice.id}`,\n\t\t\trole: 'slice',\n\t\t\ttarget: slice.id,\n\t\t\td: slice.path,\n\t\t\tfill: sceneColor(theme, slice.colorIndex),\n\t\t\tstroke: theme.background,\n\t\t\tstrokeWidth: 2,\n\t\t\tbox: { x: geo.cx - r, y: geo.cy - r, width: r * 2, height: r * 2 }\n\t\t});\n\t}\n\tmarks.push({ kind: 'circle', key: 'outer', role: 'outline', cx: geo.cx, cy: geo.cy, r: geo.outerRadius, fill: 'none', stroke: theme.nodeStroke, strokeWidth: M.outerStrokeWidth });\n\tfor (const slice of geo.slices) {\n\t\tif (!slice.drawn) continue;\n\t\tmarks.push({ kind: 'text', key: `percent:${slice.id}`, role: 'slice-label', target: slice.id, x: slice.labelX, y: slice.labelY, text: slice.labelText, anchor: 'middle', baseline: 'bottom', size: 17, fill: theme.paletteText });\n\t}\n\tfor (const entry of geo.legend) {\n\t\tmarks.push(\n\t\t\t{ kind: 'rect', key: `swatch:${entry.sliceId}`, role: 'legend', target: entry.sliceId, x: entry.x, y: entry.y, width: M.legendRectSize, height: M.legendRectSize, fill: sceneColor(theme, entry.colorIndex), stroke: sceneColor(theme, entry.colorIndex) },\n\t\t\t{\n\t\t\t\tkind: 'text',\n\t\t\t\tkey: `legend:${entry.sliceId}`,\n\t\t\t\trole: 'legend',\n\t\t\t\ttarget: entry.sliceId,\n\t\t\t\tx: entry.x + M.legendRectSize + M.legendSpacing,\n\t\t\t\ty: entry.y + M.legendRectSize / 2,\n\t\t\t\ttext: entry.text,\n\t\t\t\tanchor: 'start',\n\t\t\t\tbaseline: 'middle',\n\t\t\t\tsize: M.legendFontSize,\n\t\t\t\tfill: theme.text\n\t\t\t}\n\t\t);\n\t}\n\tif (geo.title)\n\t\tmarks.push({ kind: 'text', key: 'title', role: 'title', target: DIAGRAM_TITLE_TARGET, x: geo.title.x, y: geo.title.y, text: geo.title.text, anchor: 'middle', baseline: 'bottom', size: M.titleFontSize, fill: theme.text });\n\treturn marks;\n};\n","import { layoutChart } from '../../../chart/layout.js';\nimport { createChartSpec } from '../../../chart/spec.js';\nimport type { ChartMark, ChartSelection, ChartSpec, ChartTheme } from '../../../chart/types.js';\nimport type { DiagramGraph } from '../../../diagram/types.js';\nimport { RADAR_CHART_BOX, XY_CHART_BOX } from '../../../diagram/layout-registry.js';\nimport { DIAGRAM_TITLE_TARGET, type DiagramMark, type DiagramSceneTheme, type FamilyScene } from '../scene-kit.js';\n\n/**\n * XY and radar charts, which Ursula leaves to Mermaid's own rendering, are drawn with the\n * chart module (`layoutChart`): each series' bars, lines and points select its node.\n */\n\nconst num = (v: unknown): number | null => (typeof v === 'number' && Number.isFinite(v) ? v : null);\nconst numbers = (v: unknown): (number | null)[] => (Array.isArray(v) ? v.map(num) : []);\nconst text = (v: unknown): string => (typeof v === 'string' ? v : '');\n\nfunction chartTheme(theme: DiagramSceneTheme): Partial<ChartTheme> {\n\treturn {\n\t\tbackground: theme.background,\n\t\tsurface: theme.background,\n\t\ttext: theme.text,\n\t\tmutedText: theme.mutedText,\n\t\tgrid: theme.grid,\n\t\taxis: theme.mutedText,\n\t\tselection: theme.selection,\n\t\tfontFamily: theme.fontFamily\n\t};\n}\n\n/** The chart spec an `xychart-beta` graph describes. */\nexport function xyChartSpec(graph: DiagramGraph, palette?: readonly string[]): ChartSpec {\n\tconst data = graph.data ?? {};\n\tconst longest = Math.max(0, ...graph.nodes.map((n) => numbers(n.data?.values).length));\n\tlet categories = Array.isArray(data.categories) ? data.categories.map(String) : [];\n\tconst xMin = num(data.xMin);\n\tconst xMax = num(data.xMax);\n\tif (!categories.length && longest) {\n\t\t// A numeric x axis spreads the values evenly between its ends, as Mermaid does.\n\t\tcategories = Array.from({ length: longest }, (_, i) =>\n\t\t\txMin !== null && xMax !== null ? String(Math.round((xMin + ((xMax - xMin) * i) / Math.max(1, longest - 1)) * 100) / 100) : String(i + 1)\n\t\t);\n\t}\n\tconst bars = graph.nodes.some((n) => n.shape === 'bar');\n\treturn createChartSpec({\n\t\ttype: bars ? 'bar' : 'line',\n\t\ttitle: graph.title ?? '',\n\t\tcategories,\n\t\tseries: graph.nodes.map((n) => ({ id: n.id, name: n.label, values: numbers(n.data?.values), type: n.shape === 'line' ? 'line' : 'bar' })),\n\t\torientation: data.horizontal ? 'horizontal' : 'vertical',\n\t\txAxis: { show: true, title: text(data.xTitle), min: null, max: null, step: null, gridlines: false, format: {} },\n\t\tyAxis: { show: true, title: text(data.yTitle), min: num(data.yMin), max: num(data.yMax), step: null, gridlines: true, format: {} },\n\t\tlegend: { show: graph.nodes.some((n) => n.data?.titled), position: 'top' },\n\t\t...(palette ? { palette: [...palette] } : {})\n\t});\n}\n\n/** The chart spec a `radar-beta` graph describes. */\nexport function radarChartSpec(graph: DiagramGraph, palette?: readonly string[]): ChartSpec {\n\tconst data = graph.data ?? {};\n\treturn createChartSpec({\n\t\ttype: 'radar',\n\t\ttitle: graph.title ?? '',\n\t\tcategories: Array.isArray(data.axes) ? data.axes.map(String) : [],\n\t\tseries: graph.nodes.map((n) => ({ id: n.id, name: n.label, values: numbers(n.data?.values) })),\n\t\tyAxis: { show: true, title: '', min: num(data.min), max: num(data.max), step: null, gridlines: true, format: {} },\n\t\tlegend: { show: data.showLegend !== false, position: 'top' },\n\t\t...(palette ? { palette: [...palette] } : {})\n\t});\n}\n\nfunction targetOf(target: ChartSelection | undefined): string | undefined {\n\tif (!target) return undefined;\n\tif (target.kind === 'series' || target.kind === 'point') return target.seriesId;\n\tif (target.kind === 'title') return DIAGRAM_TITLE_TARGET;\n\treturn undefined;\n}\n\n/** Chart marks as diagram marks, moved to `(x, y)`. */\nfunction toDiagramMarks(marks: readonly ChartMark[], x: number, y: number): DiagramMark[] {\n\treturn marks\n\t\t.filter((m) => m.role !== 'selection' && m.role !== 'drag-readout')\n\t\t.map((m): DiagramMark => {\n\t\t\tconst base = { key: m.key, role: m.role, ...(targetOf(m.target) ? { target: targetOf(m.target) } : {}), ...(m.opacity !== undefined ? { opacity: m.opacity } : {}) };\n\t\t\tswitch (m.kind) {\n\t\t\t\tcase 'rect':\n\t\t\t\t\treturn { ...base, kind: 'rect', x: m.x + x, y: m.y + y, width: m.width, height: m.height, fill: m.fill, stroke: m.stroke, strokeWidth: m.strokeWidth, radius: m.radius };\n\t\t\t\tcase 'circle':\n\t\t\t\t\treturn { ...base, kind: 'circle', cx: m.cx + x, cy: m.cy + y, r: m.r, fill: m.fill, stroke: m.stroke, strokeWidth: m.strokeWidth };\n\t\t\t\tcase 'line':\n\t\t\t\t\treturn { ...base, kind: 'line', x1: m.x1 + x, y1: m.y1 + y, x2: m.x2 + x, y2: m.y2 + y, stroke: m.stroke, strokeWidth: m.strokeWidth, dash: m.dash };\n\t\t\t\tcase 'text':\n\t\t\t\t\treturn { ...base, kind: 'text', x: m.x + x, y: m.y + y, text: m.text, anchor: m.anchor, baseline: m.baseline, size: m.size, weight: m.weight, fill: m.fill, rotate: m.rotate };\n\t\t\t\tcase 'path':\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...base,\n\t\t\t\t\t\tkind: 'path',\n\t\t\t\t\t\t// Chart paths are absolute, so the whole path moves with a translate prefix.\n\t\t\t\t\t\td: x || y ? translatePath(m.d, x, y) : m.d,\n\t\t\t\t\t\tfill: m.fill,\n\t\t\t\t\t\tstroke: m.stroke,\n\t\t\t\t\t\tstrokeWidth: m.strokeWidth,\n\t\t\t\t\t\tdash: m.dash\n\t\t\t\t\t};\n\t\t\t}\n\t\t});\n}\n\n/** Moves an absolute M / L / C / Q / Z path (what the chart layout draws) by (dx, dy). */\nfunction translatePath(d: string, dx: number, dy: number): string {\n\tlet i = 0;\n\treturn d.replace(/-?\\d*\\.?\\d+(?:e-?\\d+)?/gi, (v) => String(Math.round((Number(v) + (i++ % 2 === 0 ? dx : dy)) * 100) / 100));\n}\n\nexport const xyChartScene: FamilyScene = ({ graph, theme }) => {\n\tconst box = XY_CHART_BOX;\n\tconst scene = layoutChart(xyChartSpec(graph, theme.palette), { width: box.width, height: box.height, theme: chartTheme(theme) });\n\treturn toDiagramMarks(scene.marks, box.x, box.y);\n};\n\nexport const radarScene: FamilyScene = ({ graph, theme }) => {\n\tconst box = RADAR_CHART_BOX;\n\tconst scene = layoutChart(radarChartSpec(graph, theme.palette), { width: box.width, height: box.height, theme: chartTheme(theme) });\n\treturn toDiagramMarks(scene.marks, box.x, box.y);\n};\n","// Follows Ursula's src/react/canvases/sequence.tsx (and its .ursula-seq__* styles).\nimport { SEQUENCE_METRICS, sequenceGeometry } from '../../../diagram/sequence/geometry.js';\nimport type { ArrowHead, EdgePattern, GraphEdge } from '../../../diagram/types.js';\nimport { DIAGRAM_TITLE_TARGET, estimateTextWidth, sceneColor, type DiagramMark, type DiagramSceneTheme, type FamilyScene } from '../scene-kit.js';\n\nconst M = SEQUENCE_METRICS;\n/** Participant, note and message text (Ursula caps them at 14px). */\nconst LABEL_SIZE = 14;\nconst LINE_HEIGHT = 1.25;\n/** Fragment tabs, fragment conditions. */\nconst SMALL_SIZE = 13;\nconst TITLE_SIZE = 18;\n/** Palette slot of Mermaid's yellow note colour. */\nconst NOTE_COLOR = 5;\n\n/** Label text as Ursula shows it (`displayLabel`): backticks dropped, `<br>` as new lines, entities decoded. */\nfunction displayLines(label: string): string[] {\n\tlet text = label;\n\tif (text.startsWith('`') && text.endsWith('`')) text = text.slice(1, -1);\n\treturn text\n\t\t.replace(/<br\\s*\\/?>/gi, '\\n')\n\t\t.replace(/#quot;/g, '\"')\n\t\t.replace(/#(\\d+);/g, (_, n: string) => String.fromCharCode(Number(n)))\n\t\t.split('\\n');\n}\n\nfunction dashFor(pattern: EdgePattern): string | undefined {\n\treturn pattern === 'dotted' ? '6 4' : pattern === 'invisible' ? '0 1' : undefined;\n}\n\n/** Arrow heads the mark contract draws: `->>` filled, `-)` open; crosses are drawn separately. */\nfunction markerFor(head: ArrowHead): 'arrow' | 'arrowclosed' | undefined {\n\tif (head === 'none' || head === 'arrow_cross') return undefined;\n\treturn head === 'arrow_open' ? 'arrow' : 'arrowclosed';\n}\n\n/** Lines of text centred on (x, cy), one mark per line. */\nfunction textLines(\n\tlines: string[],\n\tbase: { key: string; role: string; target?: string; x: number; cy: number; size: number; fill: string; weight?: number; anchor?: 'start' | 'middle' | 'end' }\n): DiagramMark[] {\n\tconst lh = base.size * LINE_HEIGHT;\n\tconst top = base.cy - ((lines.length - 1) * lh) / 2;\n\treturn lines.map((text, i) => ({\n\t\tkind: 'text',\n\t\tkey: lines.length === 1 ? base.key : `${base.key}:${i}`,\n\t\trole: base.role,\n\t\t...(base.target ? { target: base.target } : {}),\n\t\tx: base.x,\n\t\ty: top + i * lh,\n\t\ttext,\n\t\tanchor: base.anchor ?? 'middle',\n\t\tbaseline: 'middle',\n\t\tsize: base.size,\n\t\tfill: base.fill,\n\t\t...(base.weight ? { weight: base.weight } : {})\n\t}));\n}\n\n/** A participant box (or stick figure for `actor`) with its name; `mirror` is the copy under the lifelines. */\nfunction participantMarks(\n\tnode: { id: string; label: string; shape: string },\n\tbox: { x: number; y: number; width: number; height: number },\n\tmirror: boolean,\n\ttheme: DiagramSceneTheme\n): DiagramMark[] {\n\tconst prefix = mirror ? 'mirror' : 'participant';\n\tconst role = mirror ? 'participant-mirror' : 'participant';\n\tconst lines = displayLines(node.label || node.id);\n\tif (node.shape !== 'actor') {\n\t\treturn [\n\t\t\t{ kind: 'rect', key: `${prefix}:${node.id}`, role, target: node.id, ...box, fill: theme.nodeFill, stroke: theme.nodeStroke, strokeWidth: 1, radius: 3 },\n\t\t\t...textLines(lines, { key: `${prefix}-label:${node.id}`, role: `${role}-label`, target: node.id, x: box.x + box.width / 2, cy: box.y + box.height / 2, size: LABEL_SIZE, fill: theme.text })\n\t\t];\n\t}\n\t// Actor: a 28×34 figure (viewBox 40×46, scaled to fit) over the name, bottom-aligned with a 2px gap.\n\tconst lh = LABEL_SIZE * LINE_HEIGHT;\n\tconst labelHeight = lines.length * lh;\n\tconst scale = Math.min(28 / 40, 34 / 46);\n\tconst figTop = box.y + box.height - labelHeight - 2 - 34 + (34 - 46 * scale) / 2;\n\tconst figLeft = box.x + box.width / 2 - 20 * scale;\n\tconst fx = (v: number) => figLeft + v * scale;\n\tconst fy = (v: number) => figTop + v * scale;\n\tconst d = `M${fx(20)},${fy(15)} V${fy(31)} M${fx(6)},${fy(21)} H${fx(34)} M${fx(20)},${fy(31)} L${fx(9)},${fy(44)} M${fx(20)},${fy(31)} L${fx(31)},${fy(44)}`;\n\treturn [\n\t\t{ kind: 'circle', key: `${prefix}-head:${node.id}`, role: `${role}-figure`, target: node.id, cx: fx(20), cy: fy(8), r: 7 * scale, fill: theme.nodeFill, stroke: theme.nodeStroke, strokeWidth: 1.5 },\n\t\t{\n\t\t\tkind: 'path',\n\t\t\tkey: `${prefix}-body:${node.id}`,\n\t\t\trole: `${role}-figure`,\n\t\t\ttarget: node.id,\n\t\t\td,\n\t\t\tfill: 'none',\n\t\t\tstroke: theme.nodeStroke,\n\t\t\tstrokeWidth: 1.5,\n\t\t\tbox: { x: fx(6), y: fy(15), width: 28 * scale, height: 29 * scale }\n\t\t},\n\t\t...textLines(lines, {\n\t\t\tkey: `${prefix}-label:${node.id}`,\n\t\t\trole: `${role}-label`,\n\t\t\ttarget: node.id,\n\t\t\tx: box.x + box.width / 2,\n\t\t\tcy: box.y + box.height - labelHeight / 2,\n\t\t\tsize: LABEL_SIZE,\n\t\t\tfill: theme.text\n\t\t})\n\t];\n}\n\n/** Mermaid's `x` head: a cross at the end of a message heading in direction `dir` (±1). */\nfunction crossMark(key: string, target: string, x: number, y: number, dir: number, stroke: string): DiagramMark {\n\tconst cx = x - dir * 2.8;\n\tconst h = 2.8;\n\treturn {\n\t\tkind: 'path',\n\t\tkey,\n\t\trole: 'message-head',\n\t\ttarget,\n\t\td: `M${cx - h},${y - h} L${cx + h},${y + h} M${cx + h},${y - h} L${cx - h},${y + h}`,\n\t\tstroke,\n\t\tstrokeWidth: 1.5\n\t};\n}\n\n/**\n * Sequence diagrams as Mermaid draws them (Ursula's `canvases/sequence.tsx`): participants in\n * a row, mirrored under the lifelines, one horizontal message per row with its label and\n * (with `autonumber`) a numbered dot, notes, `alt`/`loop` fragment frames with their\n * dividers, activation bars and coloured boxes. Participants, their lifelines and mirrors\n * select the participant; a message's line, head, number and label select the message; a\n * box's frame selects its group.\n */\nexport const sequenceScene: FamilyScene = ({ graph, theme }) => {\n\tconst geo = sequenceGeometry(graph);\n\tconst marks: DiagramMark[] = [];\n\n\t// Boxes (groups), behind everything.\n\tfor (const group of graph.groups) {\n\t\tconst box = geo.boxes.get(group.id);\n\t\tif (!box) continue;\n\t\tconst color = typeof group.data?.color === 'string' ? group.data.color : undefined;\n\t\tmarks.push(\n\t\t\t{ kind: 'rect', key: `box:${group.id}`, role: 'box', target: group.id, ...box, fill: color ?? 'none', stroke: theme.groupStroke, strokeWidth: 1 },\n\t\t\t...textLines(displayLines(group.label), {\n\t\t\t\tkey: `box-label:${group.id}`,\n\t\t\t\trole: 'box-label',\n\t\t\t\ttarget: group.id,\n\t\t\t\tx: box.x + box.width / 2,\n\t\t\t\tcy: box.y + 4 + (LABEL_SIZE * LINE_HEIGHT) / 2,\n\t\t\t\tsize: LABEL_SIZE,\n\t\t\t\tweight: 600,\n\t\t\t\tfill: theme.groupText\n\t\t\t})\n\t\t);\n\t}\n\n\t// Title, centred over the participants and boxes (Ursula's title node: 56px above them).\n\tif (graph.title) {\n\t\tconst lefts = [...[...geo.columns.values()].map((c) => c.x), ...[...geo.boxes.values()].map((b) => b.x)];\n\t\tconst rights = [...[...geo.columns.values()].map((c) => c.x + c.width), ...[...geo.boxes.values()].map((b) => b.x + b.width)];\n\t\tconst tops = [0, ...[...geo.boxes.values()].map((b) => b.y)];\n\t\tconst minX = lefts.length ? Math.min(...lefts) : 0;\n\t\tconst maxX = rights.length ? Math.max(...rights) : 240;\n\t\tmarks.push({ kind: 'text', key: 'title', role: 'title', target: DIAGRAM_TITLE_TARGET, x: (minX + maxX) / 2, y: Math.min(...tops) - 38, text: graph.title, anchor: 'middle', baseline: 'middle', size: TITLE_SIZE, weight: 600, fill: theme.text });\n\t}\n\n\t// Fragments (`alt`, `loop`…): frame, keyword tab, condition and dashed dividers; outer ones first.\n\t[...geo.fragments]\n\t\t.map((f, i) => ({ f, i }))\n\t\t.sort((a, b) => a.f.depth - b.f.depth || a.i - b.i)\n\t\t.forEach(({ f, i }) => {\n\t\t\tmarks.push({ kind: 'rect', key: `fragment:${i}`, role: 'fragment', x: f.x, y: f.y, width: f.width, height: f.height, fill: 'none', stroke: theme.mutedText, strokeWidth: 1.5 });\n\t\t\tconst tabW = estimateTextWidth(f.keyword, SMALL_SIZE) + 22;\n\t\t\tconst tabH = SMALL_SIZE * LINE_HEIGHT + 4;\n\t\t\tmarks.push(\n\t\t\t\t{\n\t\t\t\t\tkind: 'path',\n\t\t\t\t\tkey: `fragment-tab:${i}`,\n\t\t\t\t\trole: 'fragment-tab',\n\t\t\t\t\td: `M${f.x},${f.y} H${f.x + tabW} V${f.y + tabH * 0.6} L${f.x + tabW - 8},${f.y + tabH} H${f.x} Z`,\n\t\t\t\t\tfill: theme.groupFill,\n\t\t\t\t\tstroke: theme.mutedText,\n\t\t\t\t\tstrokeWidth: 1.5\n\t\t\t\t},\n\t\t\t\t{ kind: 'text', key: `fragment-keyword:${i}`, role: 'fragment-keyword', x: f.x + 8, y: f.y + tabH / 2, text: f.keyword, anchor: 'start', baseline: 'middle', size: SMALL_SIZE, weight: 600, fill: theme.groupText }\n\t\t\t);\n\t\t\tif (f.text)\n\t\t\t\tmarks.push({ kind: 'text', key: `fragment-text:${i}`, role: 'fragment-text', x: f.x + f.width / 2, y: f.y + 3, text: `[${f.text}]`, anchor: 'middle', baseline: 'top', size: SMALL_SIZE, weight: 600, fill: theme.text });\n\t\t\tf.dividers.forEach((d, j) => {\n\t\t\t\tmarks.push({ kind: 'line', key: `fragment-divider:${i}:${j}`, role: 'fragment-divider', x1: f.x, y1: d.y, x2: f.x + f.width, y2: d.y, stroke: theme.mutedText, strokeWidth: 1.5, dash: '6 4' });\n\t\t\t\tif (d.text)\n\t\t\t\t\tmarks.push({ kind: 'text', key: `fragment-divider-text:${i}:${j}`, role: 'fragment-text', x: f.x + f.width / 2, y: d.y + 3, text: `[${d.text}]`, anchor: 'middle', baseline: 'top', size: SMALL_SIZE, weight: 600, fill: theme.text });\n\t\t\t});\n\t\t});\n\n\t// Lifelines.\n\tfor (const col of geo.columns.values())\n\t\tmarks.push({ kind: 'line', key: `lifeline:${col.id}`, role: 'lifeline', target: col.id, x1: col.center, y1: geo.lifelineTop, x2: col.center, y2: geo.lifelineBottom, stroke: theme.grid, strokeWidth: 1 });\n\n\t// Activation bars.\n\tgeo.activations.forEach((a, i) =>\n\t\tmarks.push({ kind: 'rect', key: `activation:${i}`, role: 'activation', x: a.x, y: a.top, width: M.activationWidth, height: Math.max(8, a.bottom - a.top), fill: theme.groupFill, stroke: theme.edge, strokeWidth: 1 })\n\t);\n\n\t// Participants, top and mirrored at the bottom.\n\tfor (const node of graph.nodes) {\n\t\tconst col = geo.columns.get(node.id);\n\t\tif (!col) continue;\n\t\tmarks.push(...participantMarks(node, { x: col.x, y: 0, width: col.width, height: M.actorHeight }, false, theme));\n\t\tmarks.push(...participantMarks(node, { x: col.x, y: geo.lifelineBottom, width: col.width, height: M.actorHeight }, true, theme));\n\t}\n\n\t// Notes: a tinted box (fill and outline drawn apart so the tint does not fade the border).\n\tconst noteColor = sceneColor(theme, NOTE_COLOR);\n\tgeo.notes.forEach((n, i) => {\n\t\tconst box = { x: n.x, y: n.y, width: n.width, height: n.height };\n\t\tmarks.push(\n\t\t\t{ kind: 'rect', key: `note-bg:${i}`, role: 'note', ...box, fill: theme.background },\n\t\t\t{ kind: 'rect', key: `note-fill:${i}`, role: 'note', ...box, fill: noteColor, opacity: 0.3 },\n\t\t\t{ kind: 'rect', key: `note:${i}`, role: 'note', ...box, fill: 'none', stroke: noteColor, strokeWidth: 1 },\n\t\t\t...textLines(displayLines(n.text), { key: `note-text:${i}`, role: 'note-text', x: n.x + n.width / 2, cy: n.y + n.height / 2, size: LABEL_SIZE, fill: theme.text })\n\t\t);\n\t});\n\n\t// Messages, above everything else.\n\tconst edges = new Map<string, GraphEdge>(graph.edges.map((e) => [e.id, e]));\n\tconst autonumber = !!graph.data?.autonumber;\n\tgeo.messages.forEach((row, index) => {\n\t\tconst edge = edges.get(row.edgeId);\n\t\tif (!edge) return;\n\t\tconst x1 = geo.columns.get(edge.source)?.center ?? 0;\n\t\tconst x2 = geo.columns.get(edge.target)?.center ?? 0;\n\t\tconst y = row.y;\n\t\tconst stroke = theme.edge;\n\t\tconst strokeWidth = edge.pattern === 'thick' ? 3.5 : 1.5;\n\t\tconst dash = dashFor(edge.pattern);\n\t\tconst markerEnd = markerFor(edge.arrowEnd);\n\t\tconst markerStart = markerFor(edge.arrowStart);\n\t\tconst heads = { ...(markerEnd ? { markerEnd } : {}), ...(markerStart ? { markerStart } : {}) };\n\t\tlet endX: number;\n\t\tlet endDir: number;\n\t\tlet startDir: number;\n\t\tlet endY = y;\n\t\tlet startY = y;\n\t\tif (row.self) {\n\t\t\tendX = x1 + 4;\n\t\t\tendDir = -1;\n\t\t\tstartDir = -1;\n\t\t\tendY = y + 10;\n\t\t\tstartY = y - 10;\n\t\t\tmarks.push({\n\t\t\t\tkind: 'path',\n\t\t\t\tkey: `message:${edge.id}`,\n\t\t\t\trole: 'message',\n\t\t\t\ttarget: edge.id,\n\t\t\t\td: `M${x1},${y - 10} H${x1 + 44} V${y + 10} H${x1 + 4}`,\n\t\t\t\tfill: 'none',\n\t\t\t\tstroke,\n\t\t\t\tstrokeWidth,\n\t\t\t\t...(dash ? { dash } : {}),\n\t\t\t\t...heads,\n\t\t\t\tbox: { x: x1, y: y - 10, width: 44, height: 20 }\n\t\t\t});\n\t\t} else {\n\t\t\tendX = x2 + (x2 > x1 ? -2 : 2);\n\t\t\tendDir = x2 >= x1 ? 1 : -1;\n\t\t\tstartDir = -endDir;\n\t\t\tmarks.push({ kind: 'line', key: `message:${edge.id}`, role: 'message', target: edge.id, x1, y1: y, x2: endX, y2: y, stroke, strokeWidth, ...(dash ? { dash } : {}), ...heads });\n\t\t}\n\t\tif (edge.arrowEnd === 'arrow_cross') marks.push(crossMark(`message-cross-end:${edge.id}`, edge.id, endX, endY, endDir, stroke));\n\t\tif (edge.arrowStart === 'arrow_cross') marks.push(crossMark(`message-cross-start:${edge.id}`, edge.id, x1, startY, startDir, stroke));\n\t\tif (autonumber)\n\t\t\tmarks.push(\n\t\t\t\t{ kind: 'circle', key: `number-dot:${edge.id}`, role: 'message-number', target: edge.id, cx: x1, cy: y, r: 10, fill: stroke },\n\t\t\t\t{ kind: 'text', key: `number:${edge.id}`, role: 'message-number', target: edge.id, x: x1, y, text: String(index + 1), anchor: 'middle', baseline: 'middle', size: 11, weight: 600, fill: theme.background }\n\t\t\t);\n\t\tconst label = edge.label ? displayLines(edge.label).join(' ') : '';\n\t\tif (label)\n\t\t\tmarks.push({\n\t\t\t\tkind: 'text',\n\t\t\t\tkey: `message-label:${edge.id}`,\n\t\t\t\trole: 'message-label',\n\t\t\t\ttarget: edge.id,\n\t\t\t\tx: row.self ? x1 + 52 + 4 : (x1 + x2) / 2,\n\t\t\t\ty: row.self ? y - 18 : y - 13,\n\t\t\t\ttext: label,\n\t\t\t\tanchor: row.self ? 'start' : 'middle',\n\t\t\t\tbaseline: 'middle',\n\t\t\t\tsize: LABEL_SIZE,\n\t\t\t\tfill: theme.text\n\t\t\t});\n\t});\n\treturn marks;\n};\n","// Follows Ursula's src/react/canvases/journey.tsx.\nimport { JOURNEY_METRICS, journeyGeometry } from '../../../diagram/journey/geometry.js';\nimport { DIAGRAM_TITLE_TARGET, estimateTextWidth, sceneColor, type DiagramMark, type DiagramSceneTheme, type FamilyScene } from '../scene-kit.js';\n\nconst M = JOURNEY_METRICS;\n/** Mermaid's journey title is bold 4ex. */\nconst TITLE_SIZE = 32;\n\n/** Actor colours follow the section colours in the palette, so a dot reads on its task. */\nfunction actorColor(theme: DiagramSceneTheme, slot: number): string {\n\treturn sceneColor(theme, M.sectionColours + slot);\n}\n\n/** Splits `text` (explicit `<br>` / newlines first) into lines no wider than `max` px. */\nfunction wrapLines(text: string, size: number, max: number): string[] {\n\tconst lines: string[] = [];\n\tfor (const para of text.replace(/<br\\s*\\/?>/gi, '\\n').split('\\n')) {\n\t\tlet line = '';\n\t\tfor (const word of para.split(/\\s+/).filter(Boolean)) {\n\t\t\tconst next = line ? `${line} ${word}` : word;\n\t\t\tif (line && estimateTextWidth(next, size) > max) {\n\t\t\t\tlines.push(line);\n\t\t\t\tline = word;\n\t\t\t} else line = next;\n\t\t}\n\t\tlines.push(line);\n\t}\n\treturn lines;\n}\n\n/** Centred, wrapped label marks inside a box (smaller text when it needs more than two lines). */\nfunction boxLabel(\n\tkey: string,\n\trole: string,\n\ttarget: string,\n\ttext: string,\n\tbox: { x: number; y: number; width: number; height: number },\n\tfill: string\n): DiagramMark[] {\n\tlet size: number = M.fontSize;\n\tlet lines = wrapLines(text, size, box.width - 10);\n\tif (lines.length > 2) {\n\t\tsize = 13;\n\t\tlines = wrapLines(text, size, box.width - 8);\n\t}\n\tconst lineHeight = size * 1.15;\n\tconst max = Math.max(1, Math.floor(box.height / lineHeight));\n\tif (lines.length > max) lines = [...lines.slice(0, max - 1), `${lines[max - 1]}…`];\n\tconst top = box.y + box.height / 2 - (lines.length * lineHeight) / 2 + lineHeight / 2;\n\treturn lines.map((line, i) => ({\n\t\tkind: 'text',\n\t\tkey: `${key}:${i}`,\n\t\trole,\n\t\ttarget,\n\t\tx: box.x + box.width / 2,\n\t\ty: top + i * lineHeight,\n\t\ttext: line,\n\t\tanchor: 'middle',\n\t\tbaseline: 'middle',\n\t\tsize,\n\t\tfill\n\t}));\n}\n\n/**\n * User journeys as Mermaid draws them (Ursula's `canvases/journey.tsx`): the title, a dashed\n * line down from every task to its face (higher score = higher face), the thick axis, the\n * actor legend on the left, section bars on top and the task boxes with their actor dots.\n * Tasks, their dots and faces select the task; a section bar selects its section.\n */\nexport const journeyScene: FamilyScene = ({ graph, theme }) => {\n\tconst geo = journeyGeometry(graph);\n\tconst marks: DiagramMark[] = [];\n\tconst ink = theme.mutedText;\n\n\tif (graph.title)\n\t\tmarks.push({\n\t\t\tkind: 'text',\n\t\t\tkey: 'title',\n\t\t\trole: 'title',\n\t\t\ttarget: DIAGRAM_TITLE_TARGET,\n\t\t\tx: geo.title.x,\n\t\t\ty: geo.title.baseline + 7,\n\t\t\ttext: graph.title,\n\t\t\tanchor: 'start',\n\t\t\tbaseline: 'bottom',\n\t\t\tsize: TITLE_SIZE,\n\t\t\tweight: 700,\n\t\t\tfill: theme.text\n\t\t});\n\n\t// Mermaid draws the dashed line from the task's top to y = 450; the box covers its top.\n\tfor (const t of geo.tasks.values())\n\t\tmarks.push({ kind: 'line', key: `task-line:${t.id}`, role: 'task-line', x1: t.face.x, y1: t.y, x2: t.face.x, y2: M.lineBottom, stroke: theme.grid, strokeWidth: 1, dash: '4 2' });\n\n\tmarks.push({\n\t\tkind: 'line',\n\t\tkey: 'axis',\n\t\trole: 'axis',\n\t\tx1: geo.axis.x1,\n\t\ty1: geo.axis.y,\n\t\tx2: geo.axis.x2,\n\t\ty2: geo.axis.y,\n\t\tstroke: theme.edge,\n\t\tstrokeWidth: M.axisWidth,\n\t\tmarkerEnd: 'arrowclosed'\n\t});\n\n\tfor (const l of geo.legend)\n\t\tmarks.push(\n\t\t\t{ kind: 'circle', key: `legend-dot:${l.name}`, role: 'legend', cx: l.x, cy: l.y, r: M.actorRadius, fill: actorColor(theme, l.colour), stroke: theme.nodeStroke, strokeWidth: 1 },\n\t\t\t{ kind: 'text', key: `legend:${l.name}`, role: 'legend', x: l.labelX, y: l.y, text: l.name, anchor: 'start', baseline: 'middle', size: M.fontSize, fill: theme.text }\n\t\t);\n\n\t// Faces (Mermaid's `drawFace`): r = 15, eyes at ±r/3, mouth by score.\n\tfor (const t of geo.tasks.values()) {\n\t\tconst { x, y } = t.face;\n\t\tconst r = M.faceRadius;\n\t\tmarks.push(\n\t\t\t{ kind: 'circle', key: `face:${t.id}`, role: 'face', target: t.id, cx: x, cy: y, r, fill: theme.nodeFill, stroke: ink, strokeWidth: 2 },\n\t\t\t{ kind: 'circle', key: `eye-l:${t.id}`, role: 'face-eye', cx: x - r / 3, cy: y - r / 3, r: 1.5, fill: ink, stroke: ink, strokeWidth: 2 },\n\t\t\t{ kind: 'circle', key: `eye-r:${t.id}`, role: 'face-eye', cx: x + r / 3, cy: y - r / 3, r: 1.5, fill: ink, stroke: ink, strokeWidth: 2 }\n\t\t);\n\t\tif (t.score > 3)\n\t\t\t// Lower half circle centred 2px below the middle.\n\t\t\tmarks.push({ kind: 'path', key: `mouth:${t.id}`, role: 'face-mouth', d: `M${x + 7},${y + 2} A7,7 0 0 1 ${x - 7},${y + 2}`, fill: 'none', stroke: ink, strokeWidth: 1, box: { x: x - 7, y: y + 2, width: 14, height: 7 } });\n\t\telse if (t.score < 3)\n\t\t\t// Upper half circle centred 7px below the middle.\n\t\t\tmarks.push({ kind: 'path', key: `mouth:${t.id}`, role: 'face-mouth', d: `M${x - 7},${y + 7} A7,7 0 0 1 ${x + 7},${y + 7}`, fill: 'none', stroke: ink, strokeWidth: 1, box: { x: x - 7, y, width: 14, height: 7 } });\n\t\telse marks.push({ kind: 'line', key: `mouth:${t.id}`, role: 'face-mouth', x1: x - 5, y1: y + 7, x2: x + 5, y2: y + 7, stroke: ink, strokeWidth: 1 });\n\t}\n\n\tfor (const group of graph.groups) {\n\t\tconst s = geo.sections.get(group.id);\n\t\tif (!s) continue;\n\t\tmarks.push({\n\t\t\tkind: 'rect',\n\t\t\tkey: `section:${s.id}`,\n\t\t\trole: 'section',\n\t\t\ttarget: s.id,\n\t\t\tx: s.x,\n\t\t\ty: s.y,\n\t\t\twidth: s.width,\n\t\t\theight: s.height,\n\t\t\tfill: sceneColor(theme, s.colour),\n\t\t\tstroke: theme.nodeStroke,\n\t\t\tstrokeWidth: 1,\n\t\t\tradius: 3,\n\t\t\t// Mermaid draws no bar for an empty section; it is parked after the tasks, faded.\n\t\t\t...(s.empty ? { dash: '4 3', opacity: 0.6 } : {})\n\t\t});\n\t\tmarks.push(...boxLabel(`section-label:${s.id}`, 'section-label', s.id, group.label, s, theme.paletteText).map((m) => (s.empty ? { ...m, opacity: 0.6 } : m)));\n\t}\n\n\tfor (const node of graph.nodes) {\n\t\tconst t = geo.tasks.get(node.id);\n\t\tif (!t) continue;\n\t\tmarks.push({\n\t\t\tkind: 'rect',\n\t\t\tkey: `task:${t.id}`,\n\t\t\trole: 'task',\n\t\t\ttarget: t.id,\n\t\t\tx: t.x,\n\t\t\ty: t.y,\n\t\t\twidth: t.width,\n\t\t\theight: t.height,\n\t\t\tfill: sceneColor(theme, t.colour),\n\t\t\tstroke: theme.nodeStroke,\n\t\t\tstrokeWidth: 1,\n\t\t\tradius: 3\n\t\t});\n\t\tmarks.push(...boxLabel(`task-label:${t.id}`, 'task-label', t.id, node.label ?? node.id, t, theme.paletteText));\n\t\tfor (const a of t.actors)\n\t\t\tmarks.push({ kind: 'circle', key: `actor:${t.id}:${a.name}`, role: 'actor', target: t.id, cx: a.x, cy: a.y, r: M.actorRadius, fill: actorColor(theme, a.colour), stroke: theme.nodeStroke, strokeWidth: 1 });\n\t}\n\treturn marks;\n};\n","// Follows Ursula's src/react/canvases/quadrant.tsx.\nimport { QUADRANT_METRICS, quadrantGeometry } from '../../../diagram/quadrant/geometry.js';\nimport { DIAGRAM_TITLE_TARGET, sceneColor, type DiagramMark, type FamilyScene } from '../scene-kit.js';\n\nconst M = QUADRANT_METRICS;\n/** Quadrant tint over the background (Mermaid's quadrant fills are light shades). */\nconst QUADRANT_TINT = 0.18;\n\n/**\n * Quadrant charts as Mermaid draws them (Ursula's `canvases/quadrant.tsx`): the chart area\n * split into four tinted quadrants with their labels, the internal and external borders, the\n * x-axis labels under the chart (above it while there are no points), the y-axis labels\n * rotated on the left, the title on top, and every point a filled circle with its label just\n * below. A quadrant (its area or label) selects its group; a point or its label selects the\n * point. Points keep their own `color`, `strokeColor` and `strokeWidth`.\n */\nexport const quadrantScene: FamilyScene = ({ graph, theme }) => {\n\tconst geo = quadrantGeometry(graph);\n\tconst marks: DiagramMark[] = [];\n\tconst groups = new Map(graph.groups.map((g) => [g.id, g]));\n\n\tfor (const q of geo.quadrants) {\n\t\tconst group = groups.get(q.id);\n\t\tmarks.push({ kind: 'rect', key: `quadrant-fill:${q.id}`, role: 'quadrant-fill', x: q.x, y: q.y, width: q.width, height: q.height, fill: sceneColor(theme, q.n - 1), opacity: QUADRANT_TINT });\n\t\t// A transparent area on top of the tint carries the selection (the tint's opacity would hide its outline).\n\t\tmarks.push({ kind: 'rect', key: `quadrant:${q.id}`, role: 'quadrant', ...(group ? { target: q.id } : {}), x: q.x, y: q.y, width: q.width, height: q.height, fill: 'transparent' });\n\t}\n\tfor (const q of geo.quadrants) {\n\t\tconst group = groups.get(q.id);\n\t\t// Unnamed quadrants (`placeholder`) show no label, as in Mermaid.\n\t\tif (!group || group.data?.placeholder || !group.label) continue;\n\t\tmarks.push({\n\t\t\tkind: 'text',\n\t\t\tkey: `quadrant-label:${q.id}`,\n\t\t\trole: 'quadrant-label',\n\t\t\ttarget: q.id,\n\t\t\tx: q.labelX,\n\t\t\ty: q.labelY,\n\t\t\ttext: group.label,\n\t\t\tanchor: 'middle',\n\t\t\tbaseline: q.labelBaseline,\n\t\t\tsize: M.quadrantLabelFontSize,\n\t\t\tfill: theme.text\n\t\t});\n\t}\n\n\tgeo.borders.forEach((b, i) =>\n\t\tmarks.push({\n\t\t\tkind: 'line',\n\t\t\tkey: `border:${i}`,\n\t\t\trole: b.external ? 'border' : 'divider',\n\t\t\tx1: b.x1,\n\t\t\ty1: b.y1,\n\t\t\tx2: b.x2,\n\t\t\ty2: b.y2,\n\t\t\tstroke: theme.nodeStroke,\n\t\t\tstrokeWidth: b.external ? M.externalBorderWidth : M.internalBorderWidth\n\t\t})\n\t);\n\n\tfor (const l of geo.axisLabels)\n\t\tmarks.push({\n\t\t\tkind: 'text',\n\t\t\tkey: `axis:${l.key}`,\n\t\t\trole: 'axis-label',\n\t\t\tx: l.x,\n\t\t\ty: l.y,\n\t\t\ttext: l.text,\n\t\t\tanchor: l.anchor,\n\t\t\tbaseline: 'top',\n\t\t\tsize: l.rotation ? M.yAxisLabelFontSize : M.xAxisLabelFontSize,\n\t\t\tfill: theme.text,\n\t\t\t...(l.rotation ? { rotate: l.rotation } : {})\n\t\t});\n\n\tif (geo.title)\n\t\tmarks.push({ kind: 'text', key: 'title', role: 'title', target: DIAGRAM_TITLE_TARGET, x: geo.title.x, y: geo.title.y, text: geo.title.text, anchor: 'middle', baseline: 'top', size: M.titleFontSize, fill: theme.text });\n\n\tfor (const p of geo.points) {\n\t\tconst fill = p.color ?? sceneColor(theme, 0);\n\t\tconst strokeWidth = p.strokeWidth ? parseFloat(p.strokeWidth) : 0;\n\t\tmarks.push({\n\t\t\tkind: 'circle',\n\t\t\tkey: `point:${p.id}`,\n\t\t\trole: 'point',\n\t\t\ttarget: p.id,\n\t\t\tcx: p.cx,\n\t\t\tcy: p.cy,\n\t\t\tr: p.radius,\n\t\t\tfill,\n\t\t\t...(Number.isFinite(strokeWidth) && strokeWidth > 0 ? { stroke: p.strokeColor ?? fill, strokeWidth } : {})\n\t\t});\n\t\tmarks.push({\n\t\t\tkind: 'text',\n\t\t\tkey: `point-label:${p.id}`,\n\t\t\trole: 'point-label',\n\t\t\ttarget: p.id,\n\t\t\tx: p.cx,\n\t\t\ty: p.cy + M.pointTextPadding,\n\t\t\ttext: p.label.replace(/<br\\s*\\/?>/gi, ' '),\n\t\t\tanchor: 'middle',\n\t\t\tbaseline: 'top',\n\t\t\tsize: M.pointLabelFontSize,\n\t\t\tfill: theme.text\n\t\t});\n\t}\n\treturn marks;\n};\n","// Follows Ursula's src/react/canvases/gitgraph.tsx.\nimport { GIT_METRICS, estimateTextWidth, gitGraphGeometry, type GitCommitGeometry, type GitDirection } from '../../../diagram/gitgraph/geometry.js';\nimport { sceneColor, type DiagramMark, type DiagramSceneTheme, type FamilyScene } from '../scene-kit.js';\n\nconst M = GIT_METRICS;\n\ntype Pt = [number, number];\n\n/** Rotates (dx, dy) by `deg` degrees (SVG / CSS sense: positive is clockwise on screen). */\nfunction rot([dx, dy]: Pt, deg: number): Pt {\n\tconst a = (deg * Math.PI) / 180;\n\tconst c = Math.cos(a);\n\tconst s = Math.sin(a);\n\treturn [dx * c - dy * s, dx * s + dy * c];\n}\n\nconst polygon = (points: Pt[]) => `M${points.map(([x, y]) => `${round(x)},${round(y)}`).join(' L')} Z`;\nconst round = (v: number) => Math.round(v * 100) / 100;\n\nfunction boxOf(points: Pt[]): { x: number; y: number; width: number; height: number } {\n\tconst xs = points.map((p) => p[0]);\n\tconst ys = points.map((p) => p[1]);\n\tconst x = Math.min(...xs);\n\tconst y = Math.min(...ys);\n\treturn { x, y, width: Math.max(...xs) - x, height: Math.max(...ys) - y };\n}\n\n/**\n * Bounds of a parent link (`M`, `L` and quarter-circle `A` commands in absolute\n * coordinates); each arc stays inside the box of its end points.\n */\nfunction arrowBox(d: string): { x: number; y: number; width: number; height: number } {\n\tconst points: Pt[] = [];\n\tfor (const [, cmd, args] of d.matchAll(/([MLA])([^MLA]*)/g)) {\n\t\tconst nums = (args!.match(/-?\\d+(?:\\.\\d+)?(?:e-?\\d+)?/gi) ?? []).map(Number);\n\t\tif (cmd === 'A') {\n\t\t\tfor (let i = 0; i + 6 < nums.length; i += 7) points.push([nums[i + 5]!, nums[i + 6]!]);\n\t\t} else for (let i = 0; i + 1 < nums.length; i += 2) points.push([nums[i]!, nums[i + 1]!]);\n\t}\n\treturn points.length ? boxOf(points) : { x: 0, y: 0, width: 0, height: 0 };\n}\n\n/** Mermaid's commit symbol per type (`drawCommitBullet`), centred on the commit. */\nfunction commitSymbol(c: GitCommitGeometry, theme: DiagramSceneTheme): DiagramMark[] {\n\tconst color = sceneColor(theme, c.color);\n\tconst base = { target: c.id };\n\tswitch (c.symbol) {\n\t\tcase 'HIGHLIGHT':\n\t\t\treturn [\n\t\t\t\t{ kind: 'rect', key: `commit:${c.id}`, role: 'commit', ...base, x: c.x - 10, y: c.y - 10, width: 20, height: 20, fill: theme.text, stroke: theme.text },\n\t\t\t\t{ kind: 'rect', key: `commit-inner:${c.id}`, role: 'commit-inner', ...base, x: c.x - 6, y: c.y - 6, width: 12, height: 12, fill: color, stroke: color }\n\t\t\t];\n\t\tcase 'CHERRY_PICK':\n\t\t\treturn [\n\t\t\t\t{ kind: 'circle', key: `commit:${c.id}`, role: 'commit', ...base, cx: c.x, cy: c.y, r: M.commitRadius, fill: theme.text },\n\t\t\t\t{ kind: 'circle', key: `cherry-l:${c.id}`, role: 'cherry', ...base, cx: c.x - 3, cy: c.y + 2, r: 2.75, fill: theme.background },\n\t\t\t\t{ kind: 'circle', key: `cherry-r:${c.id}`, role: 'cherry', ...base, cx: c.x + 3, cy: c.y + 2, r: 2.75, fill: theme.background },\n\t\t\t\t{\n\t\t\t\t\tkind: 'path',\n\t\t\t\t\tkey: `cherry-stem:${c.id}`,\n\t\t\t\t\trole: 'cherry-stem',\n\t\t\t\t\t...base,\n\t\t\t\t\td: `M${c.x + 3},${c.y + 1} L${c.x},${c.y - 5} M${c.x - 3},${c.y + 1} L${c.x},${c.y - 5}`,\n\t\t\t\t\tfill: 'none',\n\t\t\t\t\tstroke: theme.background\n\t\t\t\t}\n\t\t\t];\n\t\tdefault: {\n\t\t\tconst marks: DiagramMark[] = [{ kind: 'circle', key: `commit:${c.id}`, role: 'commit', ...base, cx: c.x, cy: c.y, r: M.commitRadius, fill: color, stroke: color }];\n\t\t\tif (c.symbol === 'MERGE')\n\t\t\t\tmarks.push({ kind: 'circle', key: `commit-inner:${c.id}`, role: 'commit-inner', ...base, cx: c.x, cy: c.y, r: 6, fill: theme.background, stroke: theme.background });\n\t\t\tif (c.symbol === 'REVERSE')\n\t\t\t\tmarks.push({\n\t\t\t\t\tkind: 'path',\n\t\t\t\t\tkey: `commit-reverse:${c.id}`,\n\t\t\t\t\trole: 'commit-reverse',\n\t\t\t\t\t...base,\n\t\t\t\t\td: `M${c.x - 5},${c.y - 5} L${c.x + 5},${c.y + 5} M${c.x - 5},${c.y + 5} L${c.x + 5},${c.y - 5}`,\n\t\t\t\t\tfill: 'none',\n\t\t\t\t\tstroke: theme.background,\n\t\t\t\t\tstrokeWidth: 3\n\t\t\t\t});\n\t\t\treturn marks;\n\t\t}\n\t}\n}\n\n/** The commit label (its id) on a faint background, rotated -45° about its right end like Mermaid. */\nfunction commitLabel(c: GitCommitGeometry, text: string, direction: GitDirection, theme: DiagramSceneTheme): DiagramMark[] {\n\tconst w = estimateTextWidth(text, M.labelFontSize);\n\tconst anchor: Pt = direction === 'LR' ? [1 - 0.026 * w, 18.1 - 0.0136 * w] : [-14.9, 9.2];\n\tconst ax = c.x + anchor[0];\n\tconst ay = c.y + anchor[1];\n\t// The label box (2px padding, 14px line) ends at the anchor; it turns about the anchor.\n\tconst corners = ([[-w - 4, -7], [0, -7], [0, 7], [-w - 4, 7]] as Pt[]).map((p) => {\n\t\tconst [dx, dy] = rot(p, -45);\n\t\treturn [ax + dx, ay + dy] as Pt;\n\t});\n\tconst [tx, ty] = rot([-2, 0], -45);\n\treturn [\n\t\t{ kind: 'path', key: `label-bg:${c.id}`, role: 'commit-label-bg', target: c.id, d: polygon(corners), fill: theme.edgeLabelBackground, opacity: 0.5 },\n\t\t{ kind: 'text', key: `label:${c.id}`, role: 'commit-label', target: c.id, x: ax + tx, y: ay + ty, text, anchor: 'end', baseline: 'middle', size: M.labelFontSize, fill: theme.text, rotate: -45 }\n\t];\n}\n\n/** A tag: a label with a pointed, punched end above the commit (`drawCommitTags`). */\nfunction commitTag(c: GitCommitGeometry, text: string, index: number, direction: GitDirection, theme: DiagramSceneTheme): DiagramMark[] {\n\tconst w = estimateTextWidth(text, M.labelFontSize);\n\tconst h2 = M.labelTextHeight / 2;\n\tconst key = `${c.id}:${index}`;\n\tlet points: Pt[];\n\tlet hole: Pt;\n\tlet label: Pt;\n\tlet rotate: number | undefined;\n\tif (direction === 'LR') {\n\t\tconst ly = -19.2 - index * 20;\n\t\tconst local: Pt[] = [\n\t\t\t[-w / 2 - 12, ly + 2],\n\t\t\t[-w / 2 - 12, ly - 2],\n\t\t\t[-w / 2 - 4, ly - h2 - 2],\n\t\t\t[w / 2 + 4, ly - h2 - 2],\n\t\t\t[w / 2 + 4, ly + h2 + 2],\n\t\t\t[-w / 2 - 4, ly + h2 + 2]\n\t\t];\n\t\tpoints = local.map(([x, y]) => [c.x + x, c.y + y]);\n\t\thole = [c.x - w / 2 - 8, c.y + ly];\n\t\tlabel = [c.x, c.y + ly];\n\t} else {\n\t\t// Vertical graphs: the tag hangs at 45° to the right of the commit.\n\t\tconst ox = c.x + 12;\n\t\tconst oy = c.y + 2 + index * 20;\n\t\tconst place = (p: Pt): Pt => {\n\t\t\tconst [dx, dy] = rot(p, 45);\n\t\t\treturn [ox + dx, oy + dy];\n\t\t};\n\t\tpoints = ([[0, 2], [0, -2], [10, -h2 - 2], [14 + w, -h2 - 2], [14 + w, h2 + 2], [10, h2 + 2]] as Pt[]).map(place);\n\t\thole = place([2, 0]);\n\t\tlabel = place([12 + w / 2, 0]);\n\t\trotate = 45;\n\t}\n\treturn [\n\t\t{ kind: 'path', key: `tag:${key}`, role: 'tag', target: c.id, d: polygon(points), fill: theme.groupFill, stroke: theme.groupStroke },\n\t\t{ kind: 'circle', key: `tag-hole:${key}`, role: 'tag-hole', target: c.id, cx: hole[0], cy: hole[1], r: 1.5, fill: theme.text },\n\t\t{\n\t\t\tkind: 'text',\n\t\t\tkey: `tag-label:${key}`,\n\t\t\trole: 'tag-label',\n\t\t\ttarget: c.id,\n\t\t\tx: label[0],\n\t\t\ty: label[1],\n\t\t\ttext,\n\t\t\tanchor: 'middle',\n\t\t\tbaseline: 'middle',\n\t\t\tsize: M.labelFontSize,\n\t\t\tfill: theme.text,\n\t\t\t...(rotate !== undefined ? { rotate } : {})\n\t\t}\n\t];\n}\n\n/**\n * Git graphs as Mermaid draws them (Ursula's `canvases/gitgraph.tsx`): a dashed lane per\n * branch with its name in a pill of the branch colour, Mermaid's thick lane-coloured parent\n * links with their arcs, commits as bullets along the lanes (normal, reverse, highlight,\n * merge and cherry-pick symbols), rotated commit labels and tags. A bullet, its label and\n * tags select the commit; a pill selects the branch; a link selects the parent edge.\n */\nexport const gitGraphScene: FamilyScene = ({ graph, theme }) => {\n\tconst geo = gitGraphGeometry(graph);\n\tconst marks: DiagramMark[] = [];\n\tfor (const lane of geo.lanes) {\n\t\tconst { x1, y1, x2, y2 } = lane.line;\n\t\tmarks.push({ kind: 'line', key: `lane:${lane.branch}`, role: 'lane', x1, y1, x2, y2, stroke: theme.grid, strokeWidth: 1, dash: '2 2' });\n\t}\n\tfor (const edge of graph.edges) {\n\t\tconst arrow = geo.arrows.get(edge.id);\n\t\tif (!arrow) continue;\n\t\tmarks.push({\n\t\t\tkind: 'path',\n\t\t\tkey: `arrow:${edge.id}`,\n\t\t\trole: 'parent-link',\n\t\t\ttarget: edge.id,\n\t\t\td: arrow.path,\n\t\t\tfill: 'none',\n\t\t\tstroke: sceneColor(theme, arrow.color),\n\t\t\tstrokeWidth: 8,\n\t\t\tbox: arrowBox(arrow.path)\n\t\t});\n\t}\n\tconst groups = new Map(graph.groups.map((g) => [g.id, g]));\n\tfor (const lane of geo.lanes) {\n\t\tconst group = groups.get(lane.branch);\n\t\tif (!group) continue;\n\t\tconst { x, y, width, height } = lane.label;\n\t\tconst color = sceneColor(theme, lane.color);\n\t\tmarks.push(\n\t\t\t{ kind: 'rect', key: `branch:${lane.branch}`, role: 'branch', target: lane.branch, x, y, width, height, fill: color, radius: 4 },\n\t\t\t{\n\t\t\t\tkind: 'text',\n\t\t\t\tkey: `branch-label:${lane.branch}`,\n\t\t\t\trole: 'branch-label',\n\t\t\t\ttarget: lane.branch,\n\t\t\t\tx: x + width / 2,\n\t\t\t\ty: y + height / 2,\n\t\t\t\ttext: group.label || group.id,\n\t\t\t\tanchor: 'middle',\n\t\t\t\tbaseline: 'middle',\n\t\t\t\tsize: M.branchFontSize,\n\t\t\t\tfill: theme.paletteText\n\t\t\t}\n\t\t);\n\t}\n\tfor (const node of graph.nodes) {\n\t\tconst c = geo.commits.get(node.id);\n\t\tif (!c) continue;\n\t\tmarks.push(...commitSymbol(c, theme));\n\t\tif (c.label !== undefined) marks.push(...commitLabel(c, c.label, geo.direction, theme));\n\t\tc.tags.forEach((t, i) => marks.push(...commitTag(c, t, i, geo.direction, theme)));\n\t}\n\treturn marks;\n};\n","// Follows Ursula's src/react/canvases/mindmap.tsx.\nimport { MINDMAP_METRICS, mindmapGeometry, type MindmapTopicGeometry } from '../../../diagram/mindmap/geometry.js';\nimport { sceneColor, type DiagramMark, type DiagramSceneTheme, type FamilyScene } from '../scene-kit.js';\n\nconst M = MINDMAP_METRICS;\n\ntype Box = { x: number; y: number; width: number; height: number };\n\nconst round = (v: number) => Math.round(v * 100) / 100;\n\n/** What a label line reads as (Ursula's `displayLabel`): no backticks, `<br>`s and `#…;` entities decoded. */\nfunction displayLabel(label: string): string {\n\tlet text = label;\n\tif (text.startsWith('`') && text.endsWith('`')) text = text.slice(1, -1);\n\treturn text\n\t\t.replace(/<br\\s*\\/?>/gi, '\\n')\n\t\t.replace(/#quot;/g, '\"')\n\t\t.replace(/#(\\d+);/g, (_, n) => String.fromCharCode(Number(n)));\n}\n\ninterface Arc {\n\trx: number;\n\try: number;\n\trotation: number;\n\tlarge: number;\n\tsweep: number;\n\tdx: number;\n\tdy: number;\n}\n\n/**\n * A closed outline of relative arcs from `(x, y)`, as path data plus its exact bounds\n * (each arc is sampled through its centre parameterisation, SVG spec F.6.5).\n */\nfunction arcOutline(x: number, y: number, arcs: Arc[]): { d: string; box: Box } {\n\tlet px = x;\n\tlet py = y;\n\tconst xs = [x];\n\tconst ys = [y];\n\tconst parts = [`M${round(x)},${round(y)}`];\n\tfor (const a of arcs) {\n\t\tparts.push(`a${round(a.rx)},${round(a.ry)} ${a.rotation} ${a.large},${a.sweep} ${round(a.dx)},${round(a.dy)}`);\n\t\tfor (const [sx, sy] of sampleArc(px, py, a)) {\n\t\t\txs.push(sx);\n\t\t\tys.push(sy);\n\t\t}\n\t\tpx += a.dx;\n\t\tpy += a.dy;\n\t}\n\t// Ursula closes with `H0 V0 Z` (back to the start).\n\tparts.push(`H${round(x)} V${round(y)} Z`);\n\tconst bx = Math.min(...xs);\n\tconst by = Math.min(...ys);\n\treturn { d: parts.join(' '), box: { x: bx, y: by, width: Math.max(...xs) - bx, height: Math.max(...ys) - by } };\n}\n\nfunction sampleArc(x1: number, y1: number, a: Arc): [number, number][] {\n\tconst x2 = x1 + a.dx;\n\tconst y2 = y1 + a.dy;\n\tlet rx = Math.abs(a.rx);\n\tlet ry = Math.abs(a.ry);\n\tif (!rx || !ry || (a.dx === 0 && a.dy === 0)) return [[x2, y2]];\n\tconst phi = (a.rotation * Math.PI) / 180;\n\tconst cos = Math.cos(phi);\n\tconst sin = Math.sin(phi);\n\tconst hx = (x1 - x2) / 2;\n\tconst hy = (y1 - y2) / 2;\n\tconst x1p = cos * hx + sin * hy;\n\tconst y1p = -sin * hx + cos * hy;\n\tconst lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);\n\tif (lambda > 1) {\n\t\trx *= Math.sqrt(lambda);\n\t\try *= Math.sqrt(lambda);\n\t}\n\tconst num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;\n\tconst den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;\n\tconst k = (a.large === a.sweep ? -1 : 1) * Math.sqrt(Math.max(0, num / den));\n\tconst cxp = (k * rx * y1p) / ry;\n\tconst cyp = (-k * ry * x1p) / rx;\n\tconst cx = cos * cxp - sin * cyp + (x1 + x2) / 2;\n\tconst cy = sin * cxp + cos * cyp + (y1 + y2) / 2;\n\tconst angle = (ux: number, uy: number, vx: number, vy: number) => Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);\n\tconst t1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);\n\tlet dt = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);\n\tif (!a.sweep && dt > 0) dt -= 2 * Math.PI;\n\tif (a.sweep && dt < 0) dt += 2 * Math.PI;\n\tconst out: [number, number][] = [];\n\tconst steps = 24;\n\tfor (let i = 1; i <= steps; i++) {\n\t\tconst t = t1 + (dt * i) / steps;\n\t\tout.push([cx + rx * Math.cos(t) * cos - ry * Math.sin(t) * sin, cy + rx * Math.cos(t) * sin + ry * Math.sin(t) * cos]);\n\t}\n\treturn out;\n}\n\nconst arc = (rx: number, ry: number, rotation: number, sweep: number, dx: number, dy: number): Arc => ({ rx, ry, rotation, large: 0, sweep, dx, dy });\n\n/** Mermaid's shape for a bracket form, in the topic's `w` × `h` box at (x0, y0). */\nfunction topicShape(t: MindmapTopicGeometry, x0: number, y0: number, fill: string, underline: string): DiagramMark[] {\n\tconst { width: w, height: h } = t;\n\tconst base = { target: t.id, role: 'topic' };\n\tconst key = `topic:${t.id}`;\n\tswitch (t.form) {\n\t\tcase 'circle':\n\t\t\treturn [{ kind: 'circle', key, ...base, cx: x0 + w / 2, cy: y0 + h / 2, r: w / 2, fill }];\n\t\tcase 'square':\n\t\t\treturn [{ kind: 'rect', key, ...base, x: x0, y: y0, width: w, height: h, fill }];\n\t\tcase 'rounded':\n\t\t\treturn [{ kind: 'rect', key, ...base, x: x0, y: y0, width: w, height: h, fill, radius: 5 }];\n\t\tcase 'hexagon': {\n\t\t\tconst m = h / 4;\n\t\t\tconst pts = [\n\t\t\t\t[m, h],\n\t\t\t\t[w - m, h],\n\t\t\t\t[w, h / 2],\n\t\t\t\t[w - m, 0],\n\t\t\t\t[m, 0],\n\t\t\t\t[0, h / 2]\n\t\t\t];\n\t\t\treturn [{ kind: 'path', key, ...base, d: `M${pts.map(([x, y]) => `${round(x0 + x!)},${round(y0 + y!)}`).join(' L')} Z`, fill }];\n\t\t}\n\t\tcase 'cloud': {\n\t\t\tconst r1 = 0.15 * w;\n\t\t\tconst r2 = 0.25 * w;\n\t\t\tconst r3 = 0.35 * w;\n\t\t\tconst r4 = 0.2 * w;\n\t\t\tconst { d, box } = arcOutline(x0, y0, [\n\t\t\t\tarc(r1, r1, 0, 1, w * 0.25, -w * 0.1),\n\t\t\t\tarc(r3, r3, 1, 1, w * 0.4, -w * 0.1),\n\t\t\t\tarc(r2, r2, 1, 1, w * 0.35, w * 0.2),\n\t\t\t\tarc(r1, r1, 1, 1, w * 0.15, h * 0.35),\n\t\t\t\tarc(r4, r4, 1, 1, -w * 0.15, h * 0.65),\n\t\t\t\tarc(r2, r1, 1, 1, -w * 0.25, w * 0.15),\n\t\t\t\tarc(r3, r3, 1, 1, -w * 0.5, 0),\n\t\t\t\tarc(r1, r1, 1, 1, -w * 0.25, -w * 0.15),\n\t\t\t\tarc(r1, r1, 1, 1, -w * 0.1, -h * 0.35),\n\t\t\t\tarc(r4, r4, 1, 1, w * 0.1, -h * 0.65)\n\t\t\t]);\n\t\t\treturn [{ kind: 'path', key, ...base, d, fill, box }];\n\t\t}\n\t\tcase 'bang': {\n\t\t\tconst r = 0.15 * w;\n\t\t\tconst { d, box } = arcOutline(x0, y0, [\n\t\t\t\tarc(r, r, 1, 0, w * 0.25, -h * 0.1),\n\t\t\t\tarc(r, r, 1, 0, w * 0.25, 0),\n\t\t\t\tarc(r, r, 1, 0, w * 0.25, 0),\n\t\t\t\tarc(r, r, 1, 0, w * 0.25, h * 0.1),\n\t\t\t\tarc(r, r, 1, 0, w * 0.15, h * 0.33),\n\t\t\t\tarc(r * 0.8, r * 0.8, 1, 0, 0, h * 0.34),\n\t\t\t\tarc(r, r, 1, 0, -w * 0.15, h * 0.33),\n\t\t\t\tarc(r, r, 1, 0, -w * 0.25, h * 0.15),\n\t\t\t\tarc(r, r, 1, 0, -w * 0.25, 0),\n\t\t\t\tarc(r, r, 1, 0, -w * 0.25, 0),\n\t\t\t\tarc(r, r, 1, 0, -w * 0.25, -h * 0.15),\n\t\t\t\tarc(r, r, 1, 0, -w * 0.1, -h * 0.33),\n\t\t\t\tarc(r * 0.8, r * 0.8, 1, 0, 0, -h * 0.34),\n\t\t\t\tarc(r, r, 1, 0, w * 0.1, -h * 0.33)\n\t\t\t]);\n\t\t\treturn [{ kind: 'path', key, ...base, d, fill, box }];\n\t\t}\n\t\tdefault:\n\t\t\t// `defaultMindmapNode`: a box with 5px corners and a line along the bottom.\n\t\t\treturn [\n\t\t\t\t{ kind: 'rect', key, ...base, x: x0, y: y0, width: w, height: h, fill, radius: 5 },\n\t\t\t\t{ kind: 'line', key: `underline:${t.id}`, role: 'topic-underline', target: t.id, x1: x0, y1: y0 + h, x2: x0 + w, y2: y0 + h, stroke: underline, strokeWidth: 3 }\n\t\t\t];\n\t}\n}\n\n/** Section colours: the root is drawn dark (Mermaid's `git0`), sections in palette order. */\nfunction topicColors(t: MindmapTopicGeometry, theme: DiagramSceneTheme): { fill: string; label: string; underline: string } {\n\t// `cScaleInv<n>` is the section hue's opposite: half way round the palette.\n\tconst underline = sceneColor(theme, t.section + Math.floor(theme.palette.length / 2));\n\tif (t.section < 0) return { fill: theme.nodeStroke, label: theme.background, underline };\n\treturn { fill: sceneColor(theme, t.section), label: theme.paletteText, underline };\n}\n\n/** A Font Awesome / Material icon class (`fa fa-book`) as one letter for the badge. */\nfunction iconGlyph(icon: string): string {\n\tconst name = icon.trim().split(/\\s+/).pop() ?? '';\n\tconst word = name.replace(/^(fa[a-z]?|mdi|bi|icon)-/, '');\n\treturn (word[0] ?? '?').toUpperCase();\n}\n\n/**\n * Mind maps as Mermaid draws them (Ursula's `canvases/mindmap.tsx`): straight branches\n * between topic centres in the section colour, thinner with depth, under the topics; the\n * root dark in the centre, every top-level branch a section in its palette colour, Mermaid's\n * node shapes per bracket form (default box with an underline, rounded, square, circle,\n * cloud, bang, hexagon), wrapped labels and an icon badge on the top-left corner. Mermaid\n * prints no title on mind maps. A topic's shape, label and badge select it.\n */\nexport const mindmapScene: FamilyScene = ({ graph, theme }) => {\n\tconst geo = mindmapGeometry(graph);\n\tconst marks: DiagramMark[] = [];\n\tfor (const e of geo.edges) {\n\t\tconst a = geo.topics.get(e.source);\n\t\tconst b = geo.topics.get(e.target);\n\t\tif (!a || !b) continue;\n\t\tmarks.push({ kind: 'line', key: `branch:${e.edgeId}`, role: 'branch', x1: a.x, y1: a.y, x2: b.x, y2: b.y, stroke: sceneColor(theme, e.section), strokeWidth: e.width });\n\t}\n\tfor (const node of graph.nodes) {\n\t\tconst t = geo.topics.get(node.id);\n\t\tif (!t) continue;\n\t\tconst x0 = t.x - t.width / 2;\n\t\tconst y0 = t.y - t.height / 2;\n\t\tconst colors = topicColors(t, theme);\n\t\tmarks.push(...topicShape(t, x0, y0, colors.fill, colors.underline));\n\t\tconst top = t.y - (t.lines.length * M.lineHeight) / 2;\n\t\tt.lines.forEach((line, i) => {\n\t\t\tconst text = displayLabel(line);\n\t\t\tif (!text) return;\n\t\t\tmarks.push({\n\t\t\t\tkind: 'text',\n\t\t\t\tkey: `label:${t.id}:${i}`,\n\t\t\t\trole: 'topic-label',\n\t\t\t\ttarget: t.id,\n\t\t\t\tx: t.x,\n\t\t\t\ty: top + (i + 0.5) * M.lineHeight,\n\t\t\t\ttext,\n\t\t\t\tanchor: 'middle',\n\t\t\t\tbaseline: 'middle',\n\t\t\t\tsize: M.fontSize,\n\t\t\t\tfill: colors.label\n\t\t\t});\n\t\t});\n\t\tconst icon = typeof node.data?.icon === 'string' ? node.data.icon : undefined;\n\t\tif (icon) {\n\t\t\tmarks.push(\n\t\t\t\t{ kind: 'circle', key: `icon:${t.id}`, role: 'icon', target: t.id, cx: x0, cy: y0, r: 11, fill: colors.fill, stroke: theme.background, strokeWidth: 2 },\n\t\t\t\t{ kind: 'text', key: `icon-glyph:${t.id}`, role: 'icon', target: t.id, x: x0, y: y0, text: iconGlyph(icon), anchor: 'middle', baseline: 'middle', size: 12, fill: colors.label }\n\t\t\t);\n\t\t}\n\t}\n\treturn marks;\n};\n","// Follows Ursula's src/react/canvases/c4.tsx (and the title node of src/react/canvas/Canvas.tsx).\nimport type { DiagramGraph } from '../../../diagram/types.js';\nimport { c4ArrowHead, c4BoundaryText, c4ElementGeometry, c4RelGeometry, type C4ElementType } from '../../../diagram/c4/geometry.js';\nimport type { DiagramLayout } from '../layout.js';\nimport { DIAGRAM_TITLE_TARGET, estimateTextWidth, type DiagramMark, type DiagramSceneTheme, type FamilyScene } from '../scene-kit.js';\n\n/** Mermaid's C4 element colours (`config.c4` `*_bg_color` / `*_border_color`), as Ursula hardcodes them. */\nconst C4_COLORS: Record<string, { bg: string; border: string }> = {\n\tperson: { bg: '#08427b', border: '#073b6f' },\n\tsystem: { bg: '#1168bd', border: '#3c7fc0' },\n\tcontainer: { bg: '#438dd5', border: '#3c7fc0' },\n\tcomponent: { bg: '#85bbf0', border: '#78a8d8' },\n\texternal_person: { bg: '#686868', border: '#8a8a8a' },\n\texternal_system: { bg: '#999999', border: '#8a8a8a' },\n\texternal_container: { bg: '#b3b3b3', border: '#a6a6a6' },\n\texternal_component: { bg: '#cccccc', border: '#bfbfbf' }\n};\nconst C4_ELEMENT_FG = '#ffffff';\n\nconst NAME_SIZE = 14;\nconst TYPE_SIZE = 10.5;\nconst DESCR_SIZE = 11.48;\nconst LINE_HEIGHT = 1.15;\nconst SECTION_GAP = 3;\nconst REL_FONT = 12;\nconst REL_LINE = 14;\nconst REL_GAP = 3;\n\nfunction colorsOf(type: C4ElementType): { bg: string; border: string } {\n\treturn C4_COLORS[`${type.external ? 'external_' : ''}${type.base}`] ?? C4_COLORS.container!;\n}\n\nconst styleOf = (data: Record<string, unknown> | undefined): Record<string, string> => (data?.c4Style ?? {}) as Record<string, string>;\n\n/** Ursula's title node: centred over the top-level items, 56px above the highest one. */\nfunction titleMark(graph: DiagramGraph, layout: DiagramLayout, theme: DiagramSceneTheme): DiagramMark | null {\n\tif (!graph.title) return null;\n\tconst groupIds = new Set(graph.groups.map((g) => g.id));\n\tconst roots = [...graph.nodes, ...graph.groups].filter((n) => !(n.parentId && groupIds.has(n.parentId)) && layout.positions[n.id] && layout.sizes[n.id]);\n\tconst minX = roots.length ? Math.min(...roots.map((n) => layout.positions[n.id]![0])) : 0;\n\tconst maxX = roots.length ? Math.max(...roots.map((n) => layout.positions[n.id]![0] + layout.sizes[n.id]!.width)) : 240;\n\tconst minY = roots.length ? Math.min(...roots.map((n) => layout.positions[n.id]![1])) : 0;\n\treturn { kind: 'text', key: 'title', role: 'title', target: DIAGRAM_TITLE_TARGET, x: (minX + maxX) / 2, y: minY - 56 + 18, text: graph.title, anchor: 'middle', baseline: 'middle', size: 18, weight: 600, fill: theme.text };\n}\n\n/**\n * C4 diagrams as Mermaid's C4 renderer draws them (Ursula's `canvases/c4.tsx`): boundaries as\n * dashed frames (deployment nodes solid) with the label, `[type]` and description centred at\n * the top; people as a head over a rounded body, systems, containers and components as rounded\n * rectangles, `*Db` as cylinders and `*Queue` as horizontal cylinders, in Mermaid's C4 colours\n * (`_Ext` grey, `UpdateElementStyle` overrides) with the bold name, the `[Type: technology]` line\n * and the description; relations as thin lines (the first straight, the rest Mermaid's curves)\n * with filled heads and the label and `[technology]` at the midpoint (numbered in `C4Dynamic`).\n * Every part of an element, boundary or relation selects it.\n */\nexport const c4Scene: FamilyScene = ({ graph, layout, theme }) => {\n\tconst marks: DiagramMark[] = [];\n\tconst box = (id: string) => {\n\t\tconst p = layout.positions[id];\n\t\tconst s = layout.sizes[id];\n\t\treturn p && s ? { x: p[0], y: p[1], width: s.width, height: s.height } : null;\n\t};\n\n\t// Boundaries, outermost first.\n\tconst groupIds = new Set(graph.groups.map((g) => g.id));\n\tconst depth = (id: string) => {\n\t\tlet d = 0;\n\t\tconst seen = new Set<string>();\n\t\tlet p = graph.groups.find((g) => g.id === id)?.parentId;\n\t\twhile (p && groupIds.has(p) && !seen.has(p)) {\n\t\t\tseen.add(p);\n\t\t\td++;\n\t\t\tp = graph.groups.find((g) => g.id === p)?.parentId;\n\t\t}\n\t\treturn d;\n\t};\n\tfor (const group of [...graph.groups].sort((a, b) => depth(a.id) - depth(b.id))) {\n\t\tconst b = box(group.id);\n\t\tif (!b) continue;\n\t\tconst text = c4BoundaryText(group);\n\t\tconst cx = b.x + b.width / 2;\n\t\tmarks.push({ kind: 'rect', key: `boundary:${group.id}`, role: 'boundary', target: group.id, ...b, radius: 2.5, fill: 'none', stroke: theme.groupStroke, strokeWidth: 1, dash: text.solid ? undefined : '7 7' });\n\t\tmarks.push({ kind: 'text', key: `boundary-label:${group.id}`, role: 'boundary-label', target: group.id, x: cx, y: b.y + text.labelY, text: text.label, anchor: 'middle', baseline: 'middle', size: 16, weight: 700, fill: theme.groupText });\n\t\tmarks.push({ kind: 'text', key: `boundary-type:${group.id}`, role: 'boundary-type', target: group.id, x: cx, y: b.y + text.typeY, text: text.type, anchor: 'middle', baseline: 'middle', size: 14, fill: theme.groupText });\n\t\tif (text.descr && text.descrY !== undefined)\n\t\t\tmarks.push({ kind: 'text', key: `boundary-descr:${group.id}`, role: 'boundary-descr', target: group.id, x: cx, y: b.y + text.descrY, text: text.descr, anchor: 'middle', baseline: 'middle', size: 12, fill: theme.groupText });\n\t}\n\n\t// Relation lines and heads (under the elements); their labels go on top, as in Mermaid.\n\tconst labels: DiagramMark[] = [];\n\tconst dynamic = graph.data?.c4Type === 'C4Dynamic';\n\tgraph.edges.forEach((edge, index) => {\n\t\tconst from = box(edge.source);\n\t\tconst to = box(edge.target);\n\t\tif (!from || !to) return;\n\t\tconst geo = c4RelGeometry(from, to, index);\n\t\tconst { start, end, control } = geo;\n\t\tconst style = styleOf(edge.data);\n\t\tconst line = style.lineColor || theme.edge;\n\t\tmarks.push({\n\t\t\tkind: 'path',\n\t\t\tkey: `relation:${edge.id}`,\n\t\t\trole: 'relation',\n\t\t\ttarget: edge.id,\n\t\t\td: control ? `M${start.x},${start.y} Q${control.x},${control.y} ${end.x},${end.y}` : `M${start.x},${start.y} L${end.x},${end.y}`,\n\t\t\tfill: 'none',\n\t\t\tstroke: line,\n\t\t\tstrokeWidth: 1\n\t\t});\n\t\tconst head = (key: string, points: string) =>\n\t\t\tmarks.push({ kind: 'path', key, role: 'arrow', target: edge.id, d: `M${points.split(' ').join(' L')} Z`, fill: line });\n\t\tif (edge.arrowEnd !== 'none') head(`arrow-end:${edge.id}`, c4ArrowHead(end, control ?? start));\n\t\tif (edge.arrowStart !== 'none') head(`arrow-start:${edge.id}`, c4ArrowHead(start, control ?? end));\n\n\t\tconst offsetX = parseInt(style.offsetX ?? '0', 10) || 0;\n\t\tconst offsetY = parseInt(style.offsetY ?? '0', 10) || 0;\n\t\tconst left = geo.label.x + offsetX;\n\t\tconst top = geo.label.y + offsetY - 7;\n\t\tconst textFill = style.textColor || theme.text;\n\t\tconst label = `${dynamic ? `${index + 1}: ` : ''}${edge.label ?? ''}`;\n\t\tconst tech = typeof edge.data?.tech === 'string' && edge.data.tech ? `[${edge.data.tech}]` : '';\n\t\tconst gridWidth = Math.max(label ? estimateTextWidth(label, REL_FONT) : 0, tech ? estimateTextWidth(tech, REL_FONT) : 0);\n\t\tif (label)\n\t\t\tlabels.push({ kind: 'text', key: `relation-label:${edge.id}`, role: 'relation-label', target: edge.id, x: left, y: top + REL_LINE / 2, text: label, anchor: 'start', baseline: 'middle', size: REL_FONT, fill: textFill });\n\t\tif (tech)\n\t\t\tlabels.push({\n\t\t\t\tkind: 'text',\n\t\t\t\tkey: `relation-tech:${edge.id}`,\n\t\t\t\trole: 'relation-tech',\n\t\t\t\ttarget: edge.id,\n\t\t\t\tx: left + gridWidth / 2,\n\t\t\t\ty: top + (label ? REL_LINE + REL_GAP : 0) + REL_LINE / 2,\n\t\t\t\ttext: tech,\n\t\t\t\tanchor: 'middle',\n\t\t\t\tbaseline: 'middle',\n\t\t\t\tsize: REL_FONT,\n\t\t\t\titalic: true,\n\t\t\t\tfill: style.textColor || theme.mutedText\n\t\t\t});\n\t});\n\n\t// Elements.\n\tfor (const node of graph.nodes) {\n\t\tconst b = box(node.id);\n\t\tif (!b) continue;\n\t\tconst geo = c4ElementGeometry(node);\n\t\tconst style = styleOf(node.data);\n\t\tconst colors = colorsOf(geo.type);\n\t\tconst fill = style.bgColor || colors.bg;\n\t\tconst stroke = style.borderColor || colors.border;\n\t\tconst fg = style.fontColor || C4_ELEMENT_FG;\n\t\tconst { x, y } = b;\n\t\tconst w = geo.width;\n\t\tconst h = geo.height;\n\t\tconst common = { target: node.id, fill, stroke, strokeWidth: 2 };\n\t\tif (geo.person) {\n\t\t\tconst { headRadius: r, bodyTop, bodyRadius } = geo.person;\n\t\t\tmarks.push(\n\t\t\t\t{ kind: 'rect', key: `element:${node.id}`, role: 'element', ...common, x, y: y + bodyTop, width: w, height: h - bodyTop, radius: bodyRadius },\n\t\t\t\t{ kind: 'circle', key: `element-head:${node.id}`, role: 'element', ...common, cx: x + w / 2, cy: y + r, r }\n\t\t\t);\n\t\t} else if (geo.shape === 'cylinder' && geo.cylinder) {\n\t\t\tconst { rx, ry } = geo.cylinder;\n\t\t\tconst body = h - 2 * ry;\n\t\t\tmarks.push({\n\t\t\t\tkind: 'path',\n\t\t\t\tkey: `element:${node.id}`,\n\t\t\t\trole: 'element',\n\t\t\t\t...common,\n\t\t\t\td: `M${x},${y + ry} a${rx},${ry} 0,0,0 ${w},0 a${rx},${ry} 0,0,0 ${-w},0 l0,${body} a${rx},${ry} 0,0,0 ${w},0 l0,${-body}`,\n\t\t\t\tbox: { x, y, width: w, height: h }\n\t\t\t});\n\t\t} else if (geo.shape === 'h-cyl' && geo.cylinder) {\n\t\t\tconst { rx, ry } = geo.cylinder;\n\t\t\tconst body = w - 2 * rx;\n\t\t\tmarks.push({\n\t\t\t\tkind: 'path',\n\t\t\t\tkey: `element:${node.id}`,\n\t\t\t\trole: 'element',\n\t\t\t\t...common,\n\t\t\t\td: `M${x + rx},${y + h} a${rx},${ry} 0,0,1 0,${-h} l${body},0 a${rx},${ry} 0,0,1 0,${h} M${x + rx + body},${y} a${rx},${ry} 0,0,0 0,${h} l${-body},0`,\n\t\t\t\tbox: { x, y, width: w, height: h }\n\t\t\t});\n\t\t} else marks.push({ kind: 'rect', key: `element:${node.id}`, role: 'element', ...common, x, y, width: w, height: h, radius: 12 });\n\n\t\t// Text: the sections stacked around the text centre, each line centred.\n\t\tlet top = y + geo.textCenter.y - geo.textHeight / 2;\n\t\tfor (const section of geo.sections) {\n\t\t\tconst size = section.part === 'label' ? NAME_SIZE : section.part === 'tech' ? TYPE_SIZE : DESCR_SIZE;\n\t\t\tconst lh = section.fontSize * LINE_HEIGHT;\n\t\t\tsection.lines.forEach((line, i) => {\n\t\t\t\tif (!line) return;\n\t\t\t\tmarks.push({\n\t\t\t\t\tkind: 'text',\n\t\t\t\t\tkey: `element-${section.part}:${node.id}:${i}`,\n\t\t\t\t\trole: section.part === 'label' ? 'element-name' : section.part === 'tech' ? 'element-type' : 'element-descr',\n\t\t\t\t\ttarget: node.id,\n\t\t\t\t\tx: x + geo.textCenter.x,\n\t\t\t\t\ty: top + (i + 0.5) * lh,\n\t\t\t\t\ttext: line,\n\t\t\t\t\tanchor: 'middle',\n\t\t\t\t\tbaseline: 'middle',\n\t\t\t\t\tsize,\n\t\t\t\t\tweight: section.bold ? 700 : undefined,\n\t\t\t\t\tfill: fg\n\t\t\t\t});\n\t\t\t});\n\t\t\ttop += section.height + SECTION_GAP;\n\t\t}\n\t}\n\n\tmarks.push(...labels);\n\tconst title = titleMark(graph, layout, theme);\n\tif (title) marks.push(title);\n\treturn marks;\n};\n","// Follows Ursula's src/react/canvases/architecture.tsx (and the title node of src/react/canvas/Canvas.tsx).\nimport type { DiagramGraph, GraphNode } from '../../../diagram/types.js';\nimport { ARCH_METRICS, architectureArrow, architectureEdgeRoute, isSide, serviceLabelLines, shiftEndpoint, type ArchSide } from '../../../diagram/architecture/geometry.js';\nimport type { DiagramLayout } from '../layout.js';\nimport { DIAGRAM_TITLE_TARGET, estimateTextWidth, type DiagramMark, type DiagramSceneTheme, type FamilyScene } from '../scene-kit.js';\n\nconst A = ARCH_METRICS;\n/** Mermaid's built-in icon square and glyph colours (Ursula's `--ursula-arch-icon-bg` / `-fg` defaults). */\nconst ICON_BG = '#087ebf';\nconst ICON_FG = '#ffffff';\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nconst r2 = (n: number) => Math.round(n * 100) / 100;\n\n/**\n * Rewrites an SVG path (M, L, H, V, C, S, Q, Z, absolute or relative; no arcs) as absolute\n * M / L / C / Q / Z commands, scaled by `s` and moved by (`ox`, `oy`), so Mermaid's 80×80 icon\n * glyphs can be drawn at any size in canvas coordinates.\n */\nfunction placePath(d: string, s: number, ox: number, oy: number): string {\n\tconst tokens = d.match(/[MmLlHhVvCcSsQqZz]|-?(?:\\d*\\.\\d+|\\d+\\.?)(?:e[-+]?\\d+)?/g) ?? [];\n\tconst out: string[] = [];\n\tconst P = (x: number, y: number) => `${r2(ox + x * s)},${r2(oy + y * s)}`;\n\tlet i = 0;\n\tlet cmd = '';\n\tlet x = 0, y = 0, sx = 0, sy = 0;\n\tlet lastC: [number, number] | null = null;\n\tconst num = () => Number(tokens[i++]);\n\twhile (i < tokens.length) {\n\t\tif (/[A-Za-z]/.test(tokens[i]!)) cmd = tokens[i++]!;\n\t\tconst rel = cmd === cmd.toLowerCase();\n\t\tconst bx = rel ? x : 0;\n\t\tconst by = rel ? y : 0;\n\t\tswitch (cmd.toUpperCase()) {\n\t\t\tcase 'M':\n\t\t\t\tx = bx + num();\n\t\t\t\ty = by + num();\n\t\t\t\tsx = x;\n\t\t\t\tsy = y;\n\t\t\t\tout.push(`M${P(x, y)}`);\n\t\t\t\tcmd = rel ? 'l' : 'L';\n\t\t\t\tlastC = null;\n\t\t\t\tbreak;\n\t\t\tcase 'L':\n\t\t\t\tx = bx + num();\n\t\t\t\ty = by + num();\n\t\t\t\tout.push(`L${P(x, y)}`);\n\t\t\t\tlastC = null;\n\t\t\t\tbreak;\n\t\t\tcase 'H':\n\t\t\t\tx = bx + num();\n\t\t\t\tout.push(`L${P(x, y)}`);\n\t\t\t\tlastC = null;\n\t\t\t\tbreak;\n\t\t\tcase 'V':\n\t\t\t\ty = (rel ? y : 0) + num();\n\t\t\t\tout.push(`L${P(x, y)}`);\n\t\t\t\tlastC = null;\n\t\t\t\tbreak;\n\t\t\tcase 'C': {\n\t\t\t\tconst c1x = bx + num(), c1y = by + num(), c2x = bx + num(), c2y = by + num();\n\t\t\t\tx = bx + num();\n\t\t\t\ty = by + num();\n\t\t\t\tout.push(`C${P(c1x, c1y)} ${P(c2x, c2y)} ${P(x, y)}`);\n\t\t\t\tlastC = [c2x, c2y];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'S': {\n\t\t\t\tconst c1x = lastC ? 2 * x - lastC[0] : x;\n\t\t\t\tconst c1y = lastC ? 2 * y - lastC[1] : y;\n\t\t\t\tconst c2x = bx + num(), c2y = by + num();\n\t\t\t\tx = bx + num();\n\t\t\t\ty = by + num();\n\t\t\t\tout.push(`C${P(c1x, c1y)} ${P(c2x, c2y)} ${P(x, y)}`);\n\t\t\t\tlastC = [c2x, c2y];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'Q': {\n\t\t\t\tconst cx = bx + num(), cy = by + num();\n\t\t\t\tx = bx + num();\n\t\t\t\ty = by + num();\n\t\t\t\tout.push(`Q${P(cx, cy)} ${P(x, y)}`);\n\t\t\t\tlastC = null;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'Z':\n\t\t\t\tx = sx;\n\t\t\t\ty = sy;\n\t\t\t\tout.push('Z');\n\t\t\t\tlastC = null;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\ti++;\n\t\t}\n\t}\n\treturn out.join(' ');\n}\n\n/** An ellipse as four cubic curves (glyph units). */\nfunction ellipse(cx: number, cy: number, rx: number, ry: number): string {\n\tconst k = 0.5523;\n\treturn `M${cx - rx},${cy} C${cx - rx},${cy - ry * k} ${cx - rx * k},${cy - ry} ${cx},${cy - ry} C${cx + rx * k},${cy - ry} ${cx + rx},${cy - ry * k} ${cx + rx},${cy} C${cx + rx},${cy + ry * k} ${cx + rx * k},${cy + ry} ${cx},${cy + ry} C${cx - rx * k},${cy + ry} ${cx - rx},${cy + ry * k} ${cx - rx},${cy} Z`;\n}\n\n/** A rounded rectangle (glyph units). */\nfunction roundRect(x: number, y: number, w: number, h: number, r: number): string {\n\treturn `M${x + r},${y} H${x + w - r} Q${x + w},${y} ${x + w},${y + r} V${y + h - r} Q${x + w},${y + h} ${x + w - r},${y + h} H${x + r} Q${x},${y + h} ${x},${y + h - r} V${y + r} Q${x},${y} ${x + r},${y} Z`;\n}\n\nconst line = (x1: number, y1: number, x2: number, y2: number) => `M${x1},${y1} L${x2},${y2}`;\n\n/** A glyph part: `stroke` parts are 2px outlines, `fill` parts solid. */\ntype GlyphPart = { d: string; paint: 'stroke' | 'fill' | 'both'; width?: number };\n\nconst BAR = (y: number): GlyphPart => ({ d: `m56.25,${y}c0,.27-.45.5-1,.5h-10.5c-.55,0-1-.23-1-.5s.45-.5,1-.5h10.5c.55,0,1,.23,1,.5Z`, paint: 'both', width: 1 });\nconst DOTS = (y: number): GlyphPart[] => [32.5, 27.5, 22.5].map((x) => ({ d: ellipse(x, y, 0.75, 0.75), paint: 'both', width: 1 }));\n\n/**\n * Mermaid's built-in architecture icons (`architectureIcons` in its renderer, 80×80), as paths:\n * the same outlines, with its `<ellipse>`, `<circle>`, `<rect>` and `<line>` elements written as\n * path data.\n */\nconst ARCH_GLYPHS: Record<string, GlyphPart[]> = {\n\tdatabase: [\n\t\t{ d: 'm20,57.86c0,3.94,8.95,7.14,20,7.14s20-3.2,20-7.14', paint: 'stroke' },\n\t\t{ d: 'm20,45.95c0,3.94,8.95,7.14,20,7.14s20-3.2,20-7.14', paint: 'stroke' },\n\t\t{ d: 'm20,34.05c0,3.94,8.95,7.14,20,7.14s20-3.2,20-7.14', paint: 'stroke' },\n\t\t{ d: ellipse(40, 22.14, 20, 7.14), paint: 'stroke' },\n\t\t{ d: line(20, 57.86, 20, 22.14), paint: 'stroke' },\n\t\t{ d: line(60, 57.86, 60, 22.14), paint: 'stroke' }\n\t],\n\tserver: [\n\t\t{ d: roundRect(17.5, 17.5, 45, 45, 2), paint: 'stroke' },\n\t\t{ d: line(17.5, 32.5, 62.5, 32.5), paint: 'stroke' },\n\t\t{ d: line(17.5, 47.5, 62.5, 47.5), paint: 'stroke' },\n\t\tBAR(25),\n\t\tBAR(40),\n\t\tBAR(55),\n\t\t...DOTS(25),\n\t\t...DOTS(40),\n\t\t...DOTS(55)\n\t],\n\tdisk: [\n\t\t{ d: roundRect(20, 15, 40, 50, 1), paint: 'stroke' },\n\t\t{ d: ellipse(24, 19.17, 0.8, 0.83), paint: 'stroke' },\n\t\t{ d: ellipse(56, 19.17, 0.8, 0.83), paint: 'stroke' },\n\t\t{ d: ellipse(24, 60.83, 0.8, 0.83), paint: 'stroke' },\n\t\t{ d: ellipse(56, 60.83, 0.8, 0.83), paint: 'stroke' },\n\t\t{ d: ellipse(40, 33.75, 14, 14.58), paint: 'stroke' },\n\t\t{ d: ellipse(40, 33.75, 4, 4.17), paint: 'both' },\n\t\t{ d: 'm37.51,42.52l-4.83,13.22c-.26.71-1.1,1.02-1.76.64l-4.18-2.42c-.66-.38-.81-1.26-.33-1.84l9.01-10.8c.88-1.05,2.56-.08,2.09,1.2Z', paint: 'fill' }\n\t],\n\tinternet: [\n\t\t{ d: ellipse(40, 40, 22.5, 22.5), paint: 'stroke' },\n\t\t{ d: line(40, 17.5, 40, 62.5), paint: 'stroke' },\n\t\t{ d: line(17.5, 40, 62.5, 40), paint: 'stroke' },\n\t\t{ d: 'm39.99,17.51c-15.28,11.1-15.28,33.88,0,44.98', paint: 'stroke' },\n\t\t{ d: 'm40.01,17.51c15.28,11.1,15.28,33.88,0,44.98', paint: 'stroke' },\n\t\t{ d: line(19.75, 30.1, 60.25, 30.1), paint: 'stroke' },\n\t\t{ d: line(19.75, 49.9, 60.25, 49.9), paint: 'stroke' }\n\t],\n\tcloud: [\n\t\t{\n\t\t\td: 'm65,47.5c0,2.76-2.24,5-5,5H20c-2.76,0-5-2.24-5-5,0-1.87,1.03-3.51,2.56-4.36-.04-.21-.06-.42-.06-.64,0-2.6,2.48-4.74,5.65-4.97,1.65-4.51,6.34-7.76,11.85-7.76.86,0,1.69.08,2.5.23,2.09-1.57,4.69-2.5,7.5-2.5,6.1,0,11.19,4.38,12.28,10.17,2.14.56,3.72,2.51,3.72,4.83,0,.03,0,.07-.01.1,2.29.46,4.01,2.48,4.01,4.9Z',\n\t\t\tpaint: 'stroke'\n\t\t}\n\t],\n\tblank: []\n};\n\n/**\n * A service or group icon at (`x`, `y`), `size` px: Mermaid's glyph on its blue square, the\n * icon name on the square for icons from an icon pack (`logos:aws-lambda`), or Mermaid's dashed\n * outline when there is no icon.\n */\nfunction iconMarks(key: string, target: string, icon: string | undefined, x: number, y: number, size: number, theme: DiagramSceneTheme): DiagramMark[] {\n\tconst s = size / 80;\n\tif (!icon)\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'path',\n\t\t\t\tkey: `icon:${key}`,\n\t\t\t\trole: 'icon',\n\t\t\t\ttarget,\n\t\t\t\td: placePath('M0,80 V5 Q0,0 5,0 H75 Q80,0 80,5 V80 Z', s, x, y),\n\t\t\t\tfill: 'none',\n\t\t\t\tstroke: theme.groupStroke,\n\t\t\t\tstrokeWidth: 2,\n\t\t\t\tdash: '8'\n\t\t\t}\n\t\t];\n\tconst marks: DiagramMark[] = [{ kind: 'rect', key: `icon:${key}`, role: 'icon', target, x, y, width: size, height: size, fill: ICON_BG }];\n\tconst glyph = ARCH_GLYPHS[icon];\n\tif (glyph) {\n\t\tglyph.forEach((part, i) =>\n\t\t\tmarks.push({\n\t\t\t\tkind: 'path',\n\t\t\t\tkey: `icon-glyph:${key}:${i}`,\n\t\t\t\trole: 'icon-glyph',\n\t\t\t\ttarget,\n\t\t\t\td: placePath(part.d, s, x, y),\n\t\t\t\tfill: part.paint === 'stroke' ? 'none' : ICON_FG,\n\t\t\t\tstroke: part.paint === 'fill' ? undefined : ICON_FG,\n\t\t\t\tstrokeWidth: (part.width ?? 2) * s\n\t\t\t})\n\t\t);\n\t\treturn marks;\n\t}\n\t// An icon Mermaid resolves from a registered icon pack: its name, broken to fit the square.\n\tconst font = 11 * s;\n\tconst perLine = Math.max(1, Math.floor((72 * s) / estimateTextWidth('m', font)));\n\tconst lines: string[] = [];\n\tfor (let i = 0; i < icon.length; i += perLine) lines.push(icon.slice(i, i + perLine));\n\tconst shown = lines.slice(0, Math.max(1, Math.floor((72 * s) / (font * 1.2))));\n\tconst lh = font * 1.2;\n\tshown.forEach((text, i) =>\n\t\tmarks.push({ kind: 'text', key: `icon-name:${key}:${i}`, role: 'icon-name', target, x: x + size / 2, y: y + size / 2 + (i - (shown.length - 1) / 2) * lh, text, anchor: 'middle', baseline: 'middle', size: font, fill: ICON_FG })\n\t);\n\treturn marks;\n}\n\n// ---------------------------------------------------------------------------\n// Scene\n// ---------------------------------------------------------------------------\n\nconst isJunction = (n: GraphNode) => n.shape === 'sm-circ' || !!n.data?.junction;\nconst iconOf = (data: Record<string, unknown> | undefined) => (typeof data?.icon === 'string' && data.icon ? data.icon : undefined);\n\n/** Ursula's title node: centred over the top-level items, 56px above the highest one. */\nfunction titleMark(graph: DiagramGraph, layout: DiagramLayout, theme: DiagramSceneTheme): DiagramMark | null {\n\tif (!graph.title) return null;\n\tconst groupIds = new Set(graph.groups.map((g) => g.id));\n\tconst roots = [...graph.nodes, ...graph.groups].filter((n) => !(n.parentId && groupIds.has(n.parentId)) && layout.positions[n.id] && layout.sizes[n.id]);\n\tconst minX = roots.length ? Math.min(...roots.map((n) => layout.positions[n.id]![0])) : 0;\n\tconst maxX = roots.length ? Math.max(...roots.map((n) => layout.positions[n.id]![0] + layout.sizes[n.id]!.width)) : 240;\n\tconst minY = roots.length ? Math.min(...roots.map((n) => layout.positions[n.id]![1])) : 0;\n\treturn { kind: 'text', key: 'title', role: 'title', target: DIAGRAM_TITLE_TARGET, x: (minX + maxX) / 2, y: minY - 56 + 18, text: graph.title, anchor: 'middle', baseline: 'middle', size: 18, weight: 600, fill: theme.text };\n}\n\n/**\n * `architecture-beta` diagrams as Mermaid draws them (Ursula's `canvases/architecture.tsx`):\n * groups as dashed frames with their icon and label at the top-left; services as Mermaid's 80px\n * icon squares (its `cloud`, `database`, `disk`, `internet` and `server` glyphs, the name on\n * the square for icon-pack icons, a dashed outline without an icon) with the label beneath;\n * junctions as points; edges leaving from their port sides as Mermaid's orthogonal lines with\n * triangular heads and the title along the line. Every part of a service, junction, group or\n * edge selects it.\n */\nexport const architectureScene: FamilyScene = ({ graph, layout, theme }) => {\n\tconst marks: DiagramMark[] = [];\n\tconst box = (id: string) => {\n\t\tconst p = layout.positions[id];\n\t\tconst s = layout.sizes[id];\n\t\treturn p && s ? { x: p[0], y: p[1], width: s.width, height: s.height } : null;\n\t};\n\n\t// Groups, outermost first.\n\tconst groupIds = new Set(graph.groups.map((g) => g.id));\n\tconst parentOf = new Map(graph.groups.map((g) => [g.id, g.parentId]));\n\tconst depth = (id: string) => {\n\t\tlet d = 0;\n\t\tconst seen = new Set<string>();\n\t\tlet p = parentOf.get(id);\n\t\twhile (p && groupIds.has(p) && !seen.has(p)) {\n\t\t\tseen.add(p);\n\t\t\td++;\n\t\t\tp = parentOf.get(p);\n\t\t}\n\t\treturn d;\n\t};\n\tfor (const group of [...graph.groups].sort((a, b) => depth(a.id) - depth(b.id))) {\n\t\tconst b = box(group.id);\n\t\tif (!b) continue;\n\t\tconst icon = iconOf(group.data);\n\t\tmarks.push({ kind: 'rect', key: `group:${group.id}`, role: 'group', target: group.id, ...b, fill: 'none', stroke: theme.groupStroke, strokeWidth: 2, dash: '8' });\n\t\tif (icon) marks.push(...iconMarks(`group:${group.id}`, group.id, icon, b.x + 1, b.y + 1, A.groupIconSize, theme));\n\t\tconst label = group.data?.untitled ? '' : group.label;\n\t\tif (label)\n\t\t\tmarks.push({ kind: 'text', key: `group-label:${group.id}`, role: 'group-label', target: group.id, x: b.x + (icon ? A.groupIconSize + 4 : 4), y: b.y + 7 + 19 / 2, text: label, anchor: 'start', baseline: 'middle', size: A.fontSize, fill: theme.groupText });\n\t}\n\n\t// Edges (under the services); titles go on top.\n\tconst labels: DiagramMark[] = [];\n\tconst junctions = new Set(graph.nodes.filter(isJunction).map((n) => n.id));\n\tconst port = (b: { x: number; y: number; width: number; height: number }, side: ArchSide) =>\n\t\tside === 'L' ? { x: b.x, y: b.y + b.height / 2 } : side === 'R' ? { x: b.x + b.width, y: b.y + b.height / 2 } : side === 'T' ? { x: b.x + b.width / 2, y: b.y } : { x: b.x + b.width / 2, y: b.y + b.height };\n\tfor (const edge of graph.edges) {\n\t\tconst from = box(edge.source);\n\t\tconst to = box(edge.target);\n\t\tif (!from || !to) continue;\n\t\tconst d = edge.data ?? {};\n\t\tconst sourceSide: ArchSide = isSide(d.sourceSide) ? d.sourceSide : 'R';\n\t\tconst targetSide: ArchSide = isSide(d.targetSide) ? d.targetSide : 'L';\n\t\tconst s = shiftEndpoint(port(from, sourceSide), sourceSide, { group: !!d.sourceGroup, junction: junctions.has(edge.source) });\n\t\tconst t = shiftEndpoint(port(to, targetSide), targetSide, { group: !!d.targetGroup, junction: junctions.has(edge.target) });\n\t\tconst route = architectureEdgeRoute(s, sourceSide, t, targetSide);\n\t\tmarks.push({ kind: 'path', key: `edge:${edge.id}`, role: 'edge', target: edge.id, d: route.points.map((p, i) => `${i ? 'L' : 'M'}${p.x},${p.y}`).join(' '), fill: 'none', stroke: theme.edge, strokeWidth: 3 });\n\t\tconst head = (key: string, points: string) => marks.push({ kind: 'path', key, role: 'arrow', target: edge.id, d: `M${points.split(' ').join(' L')} Z`, fill: theme.edge });\n\t\tif (edge.arrowStart !== 'none') head(`arrow-start:${edge.id}`, architectureArrow(s, sourceSide));\n\t\tif (edge.arrowEnd !== 'none') head(`arrow-end:${edge.id}`, architectureArrow(t, targetSide));\n\t\tif (edge.label)\n\t\t\tlabels.push({ kind: 'text', key: `edge-label:${edge.id}`, role: 'edge-label', target: edge.id, x: route.label.x, y: route.label.y, text: edge.label, anchor: 'middle', baseline: 'middle', size: A.fontSize, rotate: route.rotate || undefined, fill: theme.text });\n\t}\n\n\t// Services and junctions.\n\tfor (const node of graph.nodes) {\n\t\tconst b = box(node.id);\n\t\tif (!b) continue;\n\t\tif (isJunction(node)) {\n\t\t\tconst cx = b.x + b.width / 2;\n\t\t\tconst cy = b.y + b.height / 2;\n\t\t\t// The junction box is clickable; the dot is inset 3px as in Ursula.\n\t\t\tmarks.push(\n\t\t\t\t{ kind: 'circle', key: `junction:${node.id}`, role: 'junction', target: node.id, cx, cy, r: b.width / 2, fill: 'transparent' },\n\t\t\t\t{ kind: 'circle', key: `junction-dot:${node.id}`, role: 'junction', target: node.id, cx, cy, r: Math.max(1, b.width / 2 - 3), fill: theme.edge }\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\t\tmarks.push(...iconMarks(`service:${node.id}`, node.id, iconOf(node.data), b.x, b.y, b.width, theme));\n\t\tif (node.data?.untitled) continue;\n\t\tserviceLabelLines(node.label ?? '').forEach((text, i) =>\n\t\t\tmarks.push({ kind: 'text', key: `service-label:${node.id}:${i}`, role: 'service-label', target: node.id, x: b.x + b.width / 2, y: b.y + b.height + (i + 0.5) * A.lineHeight, text, anchor: 'middle', baseline: 'middle', size: A.fontSize, fill: theme.text })\n\t\t);\n\t}\n\n\tmarks.push(...labels);\n\tconst title = titleMark(graph, layout, theme);\n\tif (title) marks.push(title);\n\treturn marks;\n};\n","// Follows Ursula's src/react/canvases/gantt.tsx (and its .ursula-gantt__* styles).\nimport { GANTT_METRICS, ganttGeometry, type GanttRow } from '../../../diagram/gantt/geometry.js';\nimport type { GraphNode } from '../../../diagram/types.js';\nimport { DIAGRAM_TITLE_TARGET, sceneColor, type DiagramMark, type DiagramSceneTheme, type FamilyScene } from '../scene-kit.js';\n\nconst M = GANTT_METRICS;\n\n/** Mermaid's palette slot for critical tasks and the today line (red in both palettes). */\nconst CRIT = 3;\n\ntype Status = '' | 'done' | 'active' | 'crit' | 'done-crit' | 'active-crit';\n\nfunction statusOf(node: GraphNode | undefined): Status {\n\tconst tags = Array.isArray(node?.data?.tags) ? (node!.data!.tags as unknown[]).map(String) : [];\n\tconst done = tags.includes('done');\n\tconst active = tags.includes('active');\n\tconst crit = tags.includes('crit');\n\tif (crit && done) return 'done-crit';\n\tif (crit && active) return 'active-crit';\n\tif (crit) return 'crit';\n\tif (done) return 'done';\n\tif (active) return 'active';\n\treturn '';\n}\n\n/** Bar colours by status, as Mermaid's `taskBkgColor`, `activeTaskBkgColor`, `critBkgColor`… */\nfunction barColours(theme: DiagramSceneTheme, status: Status): { fill: string; stroke: string; text: string } {\n\tconst task = sceneColor(theme, 0);\n\tconst crit = sceneColor(theme, CRIT);\n\tswitch (status) {\n\t\tcase 'active':\n\t\t\treturn { fill: theme.background, stroke: task, text: theme.text };\n\t\tcase 'done':\n\t\t\treturn { fill: theme.grid, stroke: theme.mutedText, text: theme.text };\n\t\tcase 'crit':\n\t\t\treturn { fill: crit, stroke: crit, text: theme.paletteText };\n\t\tcase 'active-crit':\n\t\t\treturn { fill: theme.background, stroke: crit, text: theme.text };\n\t\tcase 'done-crit':\n\t\t\treturn { fill: theme.grid, stroke: crit, text: theme.text };\n\t\tdefault:\n\t\t\treturn { fill: task, stroke: task, text: theme.paletteText };\n\t}\n}\n\n/** Section band colours: Mermaid alternates `sectionBkgColor`, `altSectionBkgColor`, `sectionBkgColor2`. */\nfunction bandFill(theme: DiagramSceneTheme, index: number): string {\n\tconst slot = ((index % 4) + 4) % 4;\n\tif (slot === 1 || slot === 3) return theme.background;\n\treturn sceneColor(theme, slot === 0 ? 0 : 2);\n}\n\n/** Ursula's label-fit estimate: labels wider than the bar go to its right. */\nconst labelWidth = (text: string) => text.length * 6.8 + 16;\n\n/** A right-to-left step connector (React Flow's smooth step, without the rounded corners). */\nfunction stepPath(from: GanttRow, to: GanttRow): string {\n\tconst offset = 12;\n\tconst sx = from.x + from.width;\n\tconst sy = from.y + from.height / 2;\n\tconst tx = to.x;\n\tconst ty = to.y + to.height / 2;\n\tif (tx >= sx + 2 * offset) {\n\t\tconst mx = (sx + tx) / 2;\n\t\treturn `M ${sx} ${sy} L ${mx} ${sy} L ${mx} ${ty} L ${tx} ${ty}`;\n\t}\n\tconst my = (sy + ty) / 2;\n\treturn `M ${sx} ${sy} L ${sx + offset} ${sy} L ${sx + offset} ${my} L ${tx - offset} ${my} L ${tx - offset} ${ty} L ${tx} ${ty}`;\n}\n\n/**\n * Gantt charts as Mermaid draws them (Ursula's `canvases/gantt.tsx`): sections as full-width\n * bands at 20% opacity with their names on the left, grid lines with date ticks along the\n * bottom, the today line, one bar per task coloured by status (`done`, `active`, `crit` and\n * their combinations), milestones as diamonds, and the title centred above. A bar and its label\n * select the task; a band and its name select the section. `after` dependencies are not drawn by\n * Mermaid, so they appear only when they or one of their tasks is selected.\n */\nexport const ganttScene: FamilyScene = ({ graph, selection, theme }) => {\n\tconst geo = ganttGeometry(graph);\n\tconst nodes = new Map(graph.nodes.map((n) => [n.id, n]));\n\tconst marks: DiagramMark[] = [];\n\tconst left = geo.chartX - M.labelWidth;\n\tconst fullWidth = M.labelWidth + geo.chartWidth + M.rightPadding;\n\n\t// Bands (the loose band, for tasks before the first section, draws like section 0).\n\tfor (const band of geo.bands) {\n\t\tconst key = band.id ?? '::loose';\n\t\tmarks.push({\n\t\t\tkind: 'rect',\n\t\t\tkey: `band:${key}`,\n\t\t\trole: 'section',\n\t\t\ttarget: band.id,\n\t\t\tx: left,\n\t\t\ty: band.y,\n\t\t\twidth: fullWidth,\n\t\t\theight: band.height,\n\t\t\tfill: bandFill(theme, band.id ? band.index : 0),\n\t\t\topacity: 0.2\n\t\t});\n\t\tif (band.label !== undefined)\n\t\t\tmarks.push({ kind: 'text', key: `section:${key}`, role: 'section-label', target: band.id, x: left + 8, y: band.y + 4, text: band.label, anchor: 'start', baseline: 'top', size: 12, fill: theme.groupText });\n\t}\n\n\t// Grid lines, tick labels and the today line.\n\tfor (const tick of geo.ticks) {\n\t\tmarks.push(\n\t\t\t{ kind: 'line', key: `tick:${tick.time}`, role: 'grid', x1: tick.x, y1: geo.top, x2: tick.x, y2: geo.bottom, stroke: theme.grid, opacity: 0.8 },\n\t\t\t{ kind: 'text', key: `tick-label:${tick.time}`, role: 'axis', x: tick.x, y: geo.bottom + 6, text: tick.label, anchor: 'middle', baseline: 'top', size: 11, fill: theme.mutedText }\n\t\t);\n\t}\n\tif (geo.today !== undefined)\n\t\tmarks.push({ kind: 'line', key: 'today', role: 'today', x1: geo.today, y1: geo.top, x2: geo.today, y2: geo.bottom, stroke: sceneColor(theme, CRIT), strokeWidth: 2 });\n\n\t// Dependencies of the selection.\n\tfor (const edge of graph.edges) {\n\t\tif (!selection.has(edge.id) && !selection.has(edge.source) && !selection.has(edge.target)) continue;\n\t\tconst from = geo.rowById.get(edge.source);\n\t\tconst to = geo.rowById.get(edge.target);\n\t\tif (!from || !to) continue;\n\t\tmarks.push({ kind: 'path', key: `dependency:${edge.id}`, role: 'dependency', target: edge.id, d: stepPath(from, to), stroke: theme.selection, strokeWidth: 1.5, dash: '5 4', markerEnd: 'arrowclosed' });\n\t}\n\n\t// Tasks.\n\tfor (const row of geo.rows) {\n\t\tconst node = nodes.get(row.id);\n\t\tconst status = statusOf(node);\n\t\tconst colours = barColours(theme, status);\n\t\tconst label = (node?.label ?? row.id).replace(/<br\\s*\\/?>/gi, ' ');\n\t\tif (row.milestone) {\n\t\t\t// A square rotated 45° and scaled to 80%, as Ursula's diamond.\n\t\t\tconst cx = row.x + row.width / 2;\n\t\t\tconst cy = row.y + row.height / 2;\n\t\t\tconst h = (row.width * 0.8 * Math.SQRT2) / 2;\n\t\t\tconst crit = status.includes('crit');\n\t\t\tmarks.push({\n\t\t\t\tkind: 'path',\n\t\t\t\tkey: `milestone:${row.id}`,\n\t\t\t\trole: 'milestone',\n\t\t\t\ttarget: row.id,\n\t\t\t\td: `M ${cx} ${cy - h} L ${cx + h} ${cy} L ${cx} ${cy + h} L ${cx - h} ${cy} Z`,\n\t\t\t\tfill: status.startsWith('done') ? theme.grid : sceneColor(theme, 0),\n\t\t\t\tstroke: crit ? sceneColor(theme, CRIT) : status.startsWith('done') ? theme.mutedText : sceneColor(theme, 0),\n\t\t\t\tstrokeWidth: 2\n\t\t\t});\n\t\t} else {\n\t\t\tmarks.push({ kind: 'rect', key: `bar:${row.id}`, role: 'bar', target: row.id, x: row.x, y: row.y, width: row.width, height: row.height, fill: colours.fill, stroke: colours.stroke, strokeWidth: 2, radius: 3 });\n\t\t}\n\t\tconst outside = row.milestone || labelWidth(label) > row.width;\n\t\tmarks.push({\n\t\t\tkind: 'text',\n\t\t\tkey: `label:${row.id}`,\n\t\t\trole: 'task-label',\n\t\t\ttarget: row.id,\n\t\t\tx: outside ? row.x + row.width + 6 : row.x + row.width / 2,\n\t\t\ty: row.y + row.height / 2,\n\t\t\ttext: label,\n\t\t\tanchor: outside ? 'start' : 'middle',\n\t\t\tbaseline: 'middle',\n\t\t\tsize: 12,\n\t\t\tfill: outside ? theme.text : colours.text,\n\t\t\t...(row.milestone ? { italic: true } : {})\n\t\t});\n\t}\n\n\t// Title, centred above everything (Ursula's generic title node: 56px above the top).\n\tif (graph.title)\n\t\tmarks.push({ kind: 'text', key: 'title', role: 'title', target: DIAGRAM_TITLE_TARGET, x: left + fullWidth / 2, y: geo.top - 38, text: graph.title, anchor: 'middle', baseline: 'middle', size: 18, weight: 600, fill: theme.text });\n\treturn marks;\n};\n","// Follows Ursula's src/react/canvases/timeline.tsx (and its .ursula-timeline__* styles).\nimport { TIMELINE_METRICS, textWidth, timelineGeometry, type TimelineBox, type TimelineColour } from '../../../diagram/timeline/geometry.js';\nimport { DIAGRAM_TITLE_TARGET, sceneColor, type DiagramMark, type DiagramSceneTheme, type FamilyScene } from '../scene-kit.js';\n\nconst M = TIMELINE_METRICS;\nconst RADIUS = 5;\n/** Box text: 17px below the top, 16px with a 1.1 line height (Ursula's `.ursula-timeline__box`). */\nconst TEXT_TOP = 17;\nconst TEXT_LINE = M.fontSize * 1.1;\n\n/** Fill, text and bottom-line colours for a colour slot (Mermaid's `cScale`, `cScaleLabel`, `cScaleInv`). */\nfunction colours(theme: DiagramSceneTheme, colour: TimelineColour): { fill: string; text: string; line?: string } {\n\tif (colour === 'root') return { fill: theme.nodeStroke, text: theme.background };\n\treturn { fill: sceneColor(theme, colour), text: theme.paletteText, line: sceneColor(theme, colour + 6) };\n}\n\n/** A box with rounded top corners and a square bottom (Mermaid's `node-bkg` path). */\nfunction boxPath({ x, y, width: w, height: h }: TimelineBox): string {\n\tconst r = Math.min(RADIUS, w / 2, h / 2);\n\treturn `M ${x} ${y + r} Q ${x} ${y} ${x + r} ${y} L ${x + w - r} ${y} Q ${x + w} ${y} ${x + w} ${y + r} L ${x + w} ${y + h} L ${x} ${y + h} Z`;\n}\n\n/** Lines of `text` wrapped at `width` (word wrap, `<br>` breaks), like the geometry's `wrapLines`. */\nfunction wrap(text: string, width: number): string[] {\n\tconst out: string[] = [];\n\tfor (const part of String(text).split(/<br\\s*\\/?>|\\n/i)) {\n\t\tlet line = '';\n\t\tfor (const word of part.split(/\\s+/).filter(Boolean)) {\n\t\t\tconst next = line ? `${line} ${word}` : word;\n\t\t\tif (line && textWidth(next) > width) {\n\t\t\t\tout.push(line);\n\t\t\t\tline = word;\n\t\t\t} else line = next;\n\t\t}\n\t\tout.push(line);\n\t}\n\treturn out.length ? out : [''];\n}\n\n/** A line ending in Mermaid's timeline arrowhead (`M0,0 V4 L6,2 Z`, scaled by the stroke width). */\nfunction arrowLine(key: string, x1: number, y1: number, x2: number, y2: number, width: number, dashed: boolean, colour: string): DiagramMark[] {\n\tconst len = Math.hypot(x2 - x1, y2 - y1) || 1;\n\tconst ux = (x2 - x1) / len;\n\tconst uy = (y2 - y1) / len;\n\tconst bx = x2 - ux * 5 * width;\n\tconst by = y2 - uy * 5 * width;\n\tconst tx = x2 + ux * width;\n\tconst ty = y2 + uy * width;\n\treturn [\n\t\t{ kind: 'line', key, role: dashed ? 'event-line' : 'axis', x1, y1, x2, y2, stroke: colour, strokeWidth: width, ...(dashed ? { dash: '5,5' } : {}) },\n\t\t{\n\t\t\tkind: 'path',\n\t\t\tkey: `${key}:head`,\n\t\t\trole: dashed ? 'event-line' : 'axis',\n\t\t\td: `M ${bx - uy * 2 * width} ${by + ux * 2 * width} L ${tx} ${ty} L ${bx + uy * 2 * width} ${by - ux * 2 * width} Z`,\n\t\t\tfill: colour\n\t\t}\n\t];\n}\n\n/**\n * Timelines as Mermaid's left-to-right timeline draws them (Ursula's `canvases/timeline.tsx`):\n * section header bands on top, each period as a box in one row, a thick time axis with an\n * arrow under the periods, and each period's events stacked below it on a dashed line, with\n * the title left-aligned above. One colour per section, or one per period without sections.\n * A period and its events select the period; a section band selects the section.\n */\nexport const timelineScene: FamilyScene = ({ graph, theme }) => {\n\tconst geo = timelineGeometry(graph);\n\tconst marks: DiagramMark[] = [];\n\tconst nodes = new Map(graph.nodes.map((n) => [n.id, n]));\n\tconst groups = new Map(graph.groups.map((g) => [g.id, g]));\n\tconst line = theme.text;\n\n\tconst box = (key: string, role: string, target: string, b: TimelineBox, colour: TimelineColour, text: string, opacity?: number, brighten = false) => {\n\t\tconst c = colours(theme, colour);\n\t\tmarks.push({ kind: 'path', key, role, target, d: boxPath(b), fill: c.fill, ...(opacity !== undefined ? { opacity } : {}) });\n\t\t// Mermaid's `brightness(120%)` on events, as a translucent background-coloured wash.\n\t\tif (brighten) marks.push({ kind: 'path', key: `${key}:wash`, role, target, d: boxPath(b), fill: theme.background, opacity: 0.2 });\n\t\tif (c.line) marks.push({ kind: 'line', key: `${key}:line`, role, target, x1: b.x, y1: b.y + b.height, x2: b.x + b.width, y2: b.y + b.height, stroke: c.line, strokeWidth: 3 });\n\t\twrap(text, b.width - 2 * M.padding).forEach((t, i) =>\n\t\t\tmarks.push({ kind: 'text', key: `${key}:text:${i}`, role: `${role}-label`, target, x: b.x + b.width / 2, y: b.y + TEXT_TOP + TEXT_LINE * (i + 0.5), text: t, anchor: 'middle', baseline: 'middle', size: M.fontSize, fill: c.text })\n\t\t);\n\t};\n\n\t// Lines under everything else: the dashed event lines, then the axis.\n\tfor (const l of geo.eventLines) marks.push(...arrowLine(`event-line:${l.periodId}`, l.x, l.y1, l.x, l.y2, M.eventLineWidth, true, line));\n\tmarks.push(...arrowLine('axis', geo.axis.x1, geo.axis.y, geo.axis.x2, geo.axis.y, M.axisWidth, false, line));\n\n\t// Events (Mermaid draws them 20% brighter), then sections and periods.\n\tfor (const e of geo.events) box(`event:${e.periodId}:${e.index}`, 'event', e.periodId, e, e.colour, e.text, undefined, true);\n\tfor (const s of geo.sections.values()) box(`section:${s.id}`, 'section', s.id, s, s.colour, groups.get(s.id)?.label ?? s.id, s.empty ? 0.6 : undefined);\n\tfor (const p of geo.periods.values()) {\n\t\tconst node = nodes.get(p.id);\n\t\tbox(`period:${p.id}`, 'period', p.id, p, p.colour, node?.label ?? p.id);\n\t}\n\n\t// Mermaid: bold, 4ex, left-aligned at the title x, baseline 30px above the content.\n\tif (graph.title)\n\t\tmarks.push({ kind: 'text', key: 'title', role: 'title', target: DIAGRAM_TITLE_TARGET, x: geo.title.x, y: geo.title.baseline, text: graph.title, anchor: 'start', baseline: 'bottom', size: 32, weight: 700, fill: theme.text });\n\treturn marks;\n};\n","import type { DiagramFamily } from '../../../diagram/types.js';\nimport type { FamilyScene } from '../scene-kit.js';\nimport { pieScene } from './pie.js';\nimport { radarScene, xyChartScene } from './charts.js';\nimport { sequenceScene } from './sequence.js';\nimport { journeyScene } from './journey.js';\nimport { quadrantScene } from './quadrant.js';\nimport { gitGraphScene } from './gitgraph.js';\nimport { mindmapScene } from './mindmap.js';\nimport { c4Scene } from './c4.js';\nimport { architectureScene } from './architecture.js';\nimport { ganttScene } from './gantt.js';\nimport { timelineScene } from './timeline.js';\n\nexport { pieScene, radarScene, xyChartScene, sequenceScene, journeyScene, quadrantScene, gitGraphScene, mindmapScene, c4Scene, architectureScene, ganttScene, timelineScene };\nexport { radarChartSpec, xyChartSpec } from './charts.js';\n\n/** The drawings that ship with the package, by family. */\nexport const BUILTIN_FAMILY_SCENES: Partial<Record<DiagramFamily, FamilyScene>> = {\n\tpie: pieScene,\n\txychart: xyChartScene,\n\tradar: radarScene,\n\tsequence: sequenceScene,\n\tjourney: journeyScene,\n\tquadrant: quadrantScene,\n\tgitGraph: gitGraphScene,\n\tmindmap: mindmapScene,\n\tc4: c4Scene,\n\tarchitecture: architectureScene,\n\tgantt: ganttScene,\n\ttimeline: timelineScene\n};\n","// Family drawings follow Ursula's family canvases (src/react/canvases/*.tsx and\n// src/react/canvas/familyCanvas.ts), drawn as framework-free marks so React, Svelte and the\n// SVG export share one renderer per family.\nimport type { DiagramFamily, DiagramGraph, EditOp, MermaidDocument } from '../../diagram/types.js';\nimport { ensureDiagramLayout, type DiagramLayout, type DiagramLayoutOptions } from './layout.js';\nimport { BUILTIN_FAMILY_SCENES } from './families/index.js';\nimport { DIAGRAM_SCENE_LIGHT_THEME, DIAGRAM_TITLE_TARGET, estimateTextWidth, type DiagramMark, type DiagramSceneOptions, type DiagramScene, type DiagramSceneTheme, type DiagramTextMark, type FamilyScene } from './scene-kit.js';\n\nexport * from './scene-kit.js';\n\n// Seeded from a static map (not self-registering modules): the package is `sideEffects: false`.\nconst scenes = new Map<DiagramFamily, FamilyScene>(Object.entries(BUILTIN_FAMILY_SCENES) as [DiagramFamily, FamilyScene][]);\n\n/** Registers (or replaces) the drawing for a family. */\nexport function registerFamilyScene(family: DiagramFamily, scene: FamilyScene): void {\n\tscenes.set(family, scene);\n}\n\nexport function getFamilyScene(family: DiagramFamily): FamilyScene | undefined {\n\treturn scenes.get(family);\n}\n\n/** Whether a family is drawn as a scene (its own picture) instead of nodes and edges. */\nexport function hasFamilyScene(family: DiagramFamily): boolean {\n\treturn scenes.has(family);\n}\n\nfunction markBounds(mark: DiagramMark): [number, number, number, number] | null {\n\tswitch (mark.kind) {\n\t\tcase 'rect':\n\t\t\treturn [mark.x, mark.y, mark.x + mark.width, mark.y + mark.height];\n\t\tcase 'circle':\n\t\t\treturn [mark.cx - mark.r, mark.cy - mark.r, mark.cx + mark.r, mark.cy + mark.r];\n\t\tcase 'line':\n\t\t\treturn [Math.min(mark.x1, mark.x2), Math.min(mark.y1, mark.y2), Math.max(mark.x1, mark.x2), Math.max(mark.y1, mark.y2)];\n\t\tcase 'text': {\n\t\t\tconst w = estimateTextWidth(mark.text, mark.size);\n\t\t\tconst x0 = mark.anchor === 'start' ? mark.x : mark.anchor === 'end' ? mark.x - w : mark.x - w / 2;\n\t\t\tconst y0 = mark.baseline === 'top' ? mark.y : mark.baseline === 'bottom' ? mark.y - mark.size * 1.2 : mark.y - mark.size * 0.6;\n\t\t\tif (mark.rotate) {\n\t\t\t\tconst r = Math.max(w, mark.size * 1.2);\n\t\t\t\treturn [mark.x - r, mark.y - r, mark.x + r, mark.y + r];\n\t\t\t}\n\t\t\treturn [x0, y0, x0 + w, y0 + mark.size * 1.2];\n\t\t}\n\t\tcase 'path': {\n\t\t\tif (mark.box) return [mark.box.x, mark.box.y, mark.box.x + mark.box.width, mark.box.y + mark.box.height];\n\t\t\t// Absolute M / L / C / Q / Z paths list x, y pairs; arcs must give `box`.\n\t\t\tconst nums = mark.d.match(/-?\\d*\\.?\\d+(?:e-?\\d+)?/gi)?.map(Number) ?? [];\n\t\t\tif (nums.length < 2) return null;\n\t\t\tlet x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;\n\t\t\tfor (let i = 0; i + 1 < nums.length; i += 2) {\n\t\t\t\tx0 = Math.min(x0, nums[i]!);\n\t\t\t\ty0 = Math.min(y0, nums[i + 1]!);\n\t\t\t\tx1 = Math.max(x1, nums[i]!);\n\t\t\t\ty1 = Math.max(y1, nums[i + 1]!);\n\t\t\t}\n\t\t\treturn [x0, y0, x1, y1];\n\t\t}\n\t}\n}\n\n/** Bounds of a list of marks (all zero when empty). */\nexport function diagramMarksBounds(marks: readonly DiagramMark[]): { x: number; y: number; width: number; height: number } {\n\tlet x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;\n\tfor (const mark of marks) {\n\t\tconst b = markBounds(mark);\n\t\tif (!b) continue;\n\t\tx0 = Math.min(x0, b[0]);\n\t\ty0 = Math.min(y0, b[1]);\n\t\tx1 = Math.max(x1, b[2]);\n\t\ty1 = Math.max(y1, b[3]);\n\t}\n\tif (x0 === Infinity) return { x: 0, y: 0, width: 0, height: 0 };\n\treturn { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };\n}\n\nexport interface MermaidSceneOptions extends DiagramSceneOptions, DiagramLayoutOptions {\n\t/** A layout to draw instead of computing one. */\n\tlayout?: DiagramLayout;\n}\n\n/**\n * Draws a document as its family's own picture (pie wedges, Gantt bars on a date axis,\n * sequence lifelines, XY and radar charts…). Returns null for families drawn as plain nodes\n * and edges (flowchart, state, class, ER) and for text-only families.\n */\nexport function mermaidToScene(doc: MermaidDocument, options: MermaidSceneOptions = {}): DiagramScene | null {\n\tconst scene = scenes.get(doc.family);\n\tif (!scene || doc.tier !== 'editable') return null;\n\tconst layout = options.layout ?? ensureDiagramLayout(doc.graph, doc.meta, { family: doc.family, ...options });\n\treturn familySceneFromLayout(doc.family, doc.graph, layout, options);\n}\n\n/** {@link mermaidToScene} for a graph and layout already at hand (e.g. a tween frame). */\nexport function familySceneFromLayout(\n\tfamily: DiagramFamily,\n\tgraph: DiagramGraph,\n\tlayout: DiagramLayout,\n\toptions: DiagramSceneOptions = {}\n): DiagramScene | null {\n\tconst scene = scenes.get(family);\n\tif (!scene) return null;\n\tconst theme = { ...DIAGRAM_SCENE_LIGHT_THEME, ...options.theme };\n\tconst selection = new Set(options.selection ?? []);\n\tconst marks = scene({ graph, layout, theme, selection }).map((m) => (m.target && selection.has(m.target) ? { ...m, selected: true } : m));\n\treturn { family, ...diagramMarksBounds(marks), marks };\n}\n\nconst esc = (text: string) => text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\nconst num = (v: number) => String(Math.round(v * 100) / 100);\n\n/** SVG `dominant-baseline` for a text mark's baseline. */\nexport function diagramTextBaseline(baseline: DiagramTextMark['baseline']): 'hanging' | 'central' | 'text-after-edge' {\n\treturn baseline === 'top' ? 'hanging' : baseline === 'bottom' ? 'text-after-edge' : 'central';\n}\n\n/** Ids the SVG export uses for arrow markers (`<defs>` holds both). */\nexport const DIAGRAM_MARKER_IDS = { arrow: 'pd-arrow-open', arrowclosed: 'pd-arrow-closed' } as const;\n\n/** Serialises marks as SVG elements (no `<svg>` wrapper); markers refer to {@link DIAGRAM_MARKER_IDS}. */\nexport function diagramMarksToSvg(marks: readonly DiagramMark[], theme: Pick<DiagramSceneTheme, 'selection'> = DIAGRAM_SCENE_LIGHT_THEME): string {\n\tconst out: string[] = [];\n\tfor (const m of marks) {\n\t\tconst common = `${m.target ? ` data-target=\"${esc(m.target)}\"` : ''}${m.opacity !== undefined ? ` opacity=\"${num(m.opacity)}\"` : ''}`;\n\t\tconst sel = m.selected ? ` stroke=\"${esc(theme.selection)}\" stroke-width=\"2\"` : '';\n\t\tconst markers = (mk: { markerEnd?: string; markerStart?: string }) =>\n\t\t\t`${mk.markerEnd ? ` marker-end=\"url(#${DIAGRAM_MARKER_IDS[mk.markerEnd as 'arrow']})\"` : ''}${mk.markerStart ? ` marker-start=\"url(#${DIAGRAM_MARKER_IDS[mk.markerStart as 'arrow']})\"` : ''}`;\n\t\tswitch (m.kind) {\n\t\t\tcase 'rect':\n\t\t\t\tout.push(\n\t\t\t\t\t`<rect x=\"${num(m.x)}\" y=\"${num(m.y)}\" width=\"${num(Math.max(0, m.width))}\" height=\"${num(Math.max(0, m.height))}\"${m.radius ? ` rx=\"${num(m.radius)}\"` : ''} fill=\"${esc(m.fill)}\"${sel || (m.stroke ? ` stroke=\"${esc(m.stroke)}\" stroke-width=\"${num(m.strokeWidth ?? 1)}\"` : '')}${m.dash ? ` stroke-dasharray=\"${m.dash}\"` : ''}${common}/>`\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase 'circle':\n\t\t\t\tout.push(\n\t\t\t\t\t`<circle cx=\"${num(m.cx)}\" cy=\"${num(m.cy)}\" r=\"${num(m.r)}\" fill=\"${esc(m.fill)}\"${sel || (m.stroke ? ` stroke=\"${esc(m.stroke)}\" stroke-width=\"${num(m.strokeWidth ?? 1)}\"` : '')}${common}/>`\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase 'line':\n\t\t\t\tout.push(\n\t\t\t\t\t`<line x1=\"${num(m.x1)}\" y1=\"${num(m.y1)}\" x2=\"${num(m.x2)}\" y2=\"${num(m.y2)}\" stroke=\"${esc(m.selected ? theme.selection : m.stroke)}\" stroke-width=\"${num(m.strokeWidth ?? 1)}\"${m.dash ? ` stroke-dasharray=\"${m.dash}\"` : ''}${markers(m)}${common}/>`\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase 'path':\n\t\t\t\tout.push(\n\t\t\t\t\t`<path d=\"${m.d}\" fill=\"${esc(m.fill ?? 'none')}\"${sel || (m.stroke ? ` stroke=\"${esc(m.stroke)}\" stroke-width=\"${num(m.strokeWidth ?? 1)}\"` : '')}${m.dash ? ` stroke-dasharray=\"${m.dash}\"` : ''}${markers(m)}${common}/>`\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase 'text':\n\t\t\t\tout.push(\n\t\t\t\t\t`<text x=\"${num(m.x)}\" y=\"${num(m.y)}\" text-anchor=\"${m.anchor}\" dominant-baseline=\"${diagramTextBaseline(m.baseline)}\" font-size=\"${num(m.size)}\"${m.weight ? ` font-weight=\"${m.weight}\"` : ''}${m.italic ? ' font-style=\"italic\"' : ''} fill=\"${esc(m.fill)}\"${m.rotate ? ` transform=\"rotate(${num(m.rotate)} ${num(m.x)} ${num(m.y)})\"` : ''}${common}>${esc(m.text)}</text>`\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn out.join('');\n}\n\n/**\n * A scene theme that reads the page's `--background`, `--foreground`, `--muted-foreground`,\n * `--border` and `--primary` colour variables, with light fallbacks. The React and Svelte\n * scenes use it (they paint with `style`, where `var()` works); files and PNGs use\n * {@link DIAGRAM_SCENE_LIGHT_THEME} or the dark theme.\n */\nexport const CSS_VARIABLE_DIAGRAM_SCENE_THEME: DiagramSceneTheme = {\n\t...DIAGRAM_SCENE_LIGHT_THEME,\n\tbackground: 'var(--background, #ffffff)',\n\tnodeFill: 'var(--background, #ffffff)',\n\tnodeStroke: 'var(--foreground, #0f172a)',\n\ttext: 'var(--foreground, #0f172a)',\n\tedge: 'var(--muted-foreground, #64748b)',\n\tedgeLabelBackground: 'var(--background, #ffffff)',\n\tgroupFill: 'var(--muted, #f8fafc)',\n\tgroupStroke: 'var(--border, #cbd5e1)',\n\tgroupText: 'var(--muted-foreground, #475569)',\n\tgrid: 'var(--border, #cbd5e1)',\n\tmutedText: 'var(--muted-foreground, #64748b)',\n\tselection: 'var(--primary, #2563eb)',\n\tfontFamily: 'inherit'\n};\n\n/**\n * The edit that renames what a scene mark targets: the title (`setTitle`), a group\n * (`renameGroup`), an edge (`setEdgeLabel`) or a node (`renameNode`). Null for an unknown\n * target. An empty title removes it. Shared by the React and Svelte scenes' double-click.\n */\nexport function diagramRenameOp(doc: Pick<MermaidDocument, 'graph'>, target: string, label: string): EditOp | null {\n\tconst { graph } = doc;\n\tif (target === DIAGRAM_TITLE_TARGET) return { type: 'setTitle', title: label.trim() || undefined };\n\tif (graph.groups.some((g) => g.id === target)) return { type: 'renameGroup', id: target, label };\n\tif (graph.edges.some((e) => e.id === target)) return { type: 'setEdgeLabel', id: target, label };\n\tif (graph.nodes.some((n) => n.id === target)) return { type: 'renameNode', id: target, label };\n\treturn null;\n}\n\n/** The text a rename of `target` starts from (title, group, edge or node label). */\nexport function diagramTargetLabel(doc: Pick<MermaidDocument, 'graph'>, target: string): string | null {\n\tconst { graph } = doc;\n\tif (target === DIAGRAM_TITLE_TARGET) return graph.title ?? '';\n\treturn graph.groups.find((g) => g.id === target)?.label ?? graph.edges.find((e) => e.id === target)?.label ?? graph.nodes.find((n) => n.id === target)?.label ?? null;\n}\n","// Ported from Ursula's ShapeSvg (src/react/nodes/ShapeSvg.tsx), as path data so the React\n// and Svelte nodes draw the same geometry.\n\n/**\n * How a path is painted: `shape` with the node's fill and border, `line` with the border\n * only, `filled` solid in the border colour (state start / end dots, fork bars), `hidden`\n * not at all (text nodes keep a hit area).\n */\nexport type MermaidShapePaint = 'shape' | 'line' | 'filled' | 'hidden';\n\nexport interface MermaidShapePath {\n\td: string;\n\tpaint: MermaidShapePaint;\n}\n\ntype Pt = [number, number];\n\nconst n = (v: number) => Math.round(v * 100) / 100;\nconst poly = (points: Pt[]) => `M${points.map(([x, y]) => `${n(x)},${n(y)}`).join(' L')} Z`;\nconst rect = (x: number, y: number, w: number, h: number, r = 0) => {\n\tif (r <= 0) return poly([[x, y], [x + w, y], [x + w, y + h], [x, y + h]]);\n\tconst rr = Math.min(r, w / 2, h / 2);\n\treturn `M${n(x + rr)},${n(y)} H${n(x + w - rr)} A${n(rr)},${n(rr)} 0 0 1 ${n(x + w)},${n(y + rr)} V${n(y + h - rr)} A${n(rr)},${n(rr)} 0 0 1 ${n(x + w - rr)},${n(y + h)} H${n(x + rr)} A${n(rr)},${n(rr)} 0 0 1 ${n(x)},${n(y + h - rr)} V${n(y + rr)} A${n(rr)},${n(rr)} 0 0 1 ${n(x + rr)},${n(y)} Z`;\n};\nconst ellipse = (cx: number, cy: number, rx: number, ry: number) =>\n\t`M${n(cx - rx)},${n(cy)} A${n(rx)},${n(ry)} 0 1 0 ${n(cx + rx)},${n(cy)} A${n(rx)},${n(ry)} 0 1 0 ${n(cx - rx)},${n(cy)} Z`;\nconst line = (x1: number, y1: number, x2: number, y2: number) => `M${n(x1)},${n(y1)} L${n(x2)},${n(y2)}`;\n\n/**\n * Draws a Mermaid flowchart / state / class / ER shape (canonical names, see\n * `FLOWCHART_SHAPES` and `STATE_SHAPES`) as SVG paths that fill a `width × height`\n * box, inset by `inset` so strokes are not clipped. Unknown shapes draw a rectangle.\n */\nexport function mermaidShapePaths(shape: string, width: number, height: number, inset = 1): MermaidShapePath[] {\n\tconst s = inset;\n\tconst W = Math.max(0, width - 2 * s);\n\tconst H = Math.max(0, height - 2 * s);\n\tconst x0 = s;\n\tconst y0 = s;\n\tconst x1 = width - s;\n\tconst y1 = height - s;\n\tconst cx = width / 2;\n\tconst cy = height / 2;\n\tconst fill = (d: string): MermaidShapePath => ({ d, paint: 'shape' });\n\tconst stroke = (d: string): MermaidShapePath => ({ d, paint: 'line' });\n\tconst solid = (d: string): MermaidShapePath => ({ d, paint: 'filled' });\n\tswitch (shape) {\n\t\tcase 'rounded':\n\t\tcase 'event':\n\t\t\treturn [fill(rect(x0, y0, W, H, Math.min(10, H / 4)))];\n\t\tcase 'stadium':\n\t\t\treturn [fill(rect(x0, y0, W, H, H / 2))];\n\t\tcase 'subproc':\n\t\tcase 'subroutine': {\n\t\t\tconst i = Math.min(10, W / 8);\n\t\t\treturn [fill(rect(x0, y0, W, H)), stroke(line(x0 + i, y0, x0 + i, y1)), stroke(line(x1 - i, y0, x1 - i, y1))];\n\t\t}\n\t\tcase 'cyl':\n\t\tcase 'cylinder': {\n\t\t\tconst ry = Math.min(H / 6, 14);\n\t\t\treturn [\n\t\t\t\tfill(`M${n(x0)},${n(y0 + ry)} A${n(W / 2)},${n(ry)} 0 0 1 ${n(x1)},${n(y0 + ry)} L${n(x1)},${n(y1 - ry)} A${n(W / 2)},${n(ry)} 0 0 1 ${n(x0)},${n(y1 - ry)} Z`),\n\t\t\t\tstroke(`M${n(x0)},${n(y0 + ry)} A${n(W / 2)},${n(ry)} 0 0 0 ${n(x1)},${n(y0 + ry)}`)\n\t\t\t];\n\t\t}\n\t\tcase 'h-cyl':\n\t\tcase 'lin-cyl': {\n\t\t\tconst rx = Math.min(W / 6, 14);\n\t\t\tconst paths = [\n\t\t\t\tfill(`M${n(x0 + rx)},${n(y0)} L${n(x1 - rx)},${n(y0)} A${n(rx)},${n(H / 2)} 0 0 1 ${n(x1 - rx)},${n(y1)} L${n(x0 + rx)},${n(y1)} A${n(rx)},${n(H / 2)} 0 0 1 ${n(x0 + rx)},${n(y0)} Z`),\n\t\t\t\tstroke(ellipse(x1 - rx, cy, rx, H / 2))\n\t\t\t];\n\t\t\tif (shape === 'lin-cyl') paths.push(stroke(line(x0 + rx * 2, y0, x0 + rx * 2, y1)));\n\t\t\treturn paths;\n\t\t}\n\t\tcase 'circle':\n\t\tcase 'circ':\n\t\t\treturn [fill(ellipse(cx, cy, W / 2, H / 2))];\n\t\tcase 'dbl-circ':\n\t\t\treturn [fill(ellipse(cx, cy, W / 2, H / 2)), stroke(ellipse(cx, cy, Math.max(0, W / 2 - 5), Math.max(0, H / 2 - 5)))];\n\t\tcase 'sm-circ':\n\t\tcase 'f-circ':\n\t\tcase 'stateStart':\n\t\t\treturn [solid(ellipse(cx, cy, W / 2, H / 2))];\n\t\tcase 'fr-circ':\n\t\tcase 'stateEnd':\n\t\t\treturn [fill(ellipse(cx, cy, W / 2, H / 2)), solid(ellipse(cx, cy, Math.max(0, W / 2 - 5), Math.max(0, H / 2 - 5)))];\n\t\tcase 'cross-circ':\n\t\t\treturn [\n\t\t\t\tfill(ellipse(cx, cy, W / 2, H / 2)),\n\t\t\t\tstroke(line(cx - W / 2.83, cy - H / 2.83, cx + W / 2.83, cy + H / 2.83)),\n\t\t\t\tstroke(line(cx + W / 2.83, cy - H / 2.83, cx - W / 2.83, cy + H / 2.83))\n\t\t\t];\n\t\tcase 'odd':\n\t\tcase 'asymmetric': {\n\t\t\tconst d = Math.min(H / 2, 20);\n\t\t\treturn [fill(poly([[x0 + d, y0], [x1, y0], [x1, y1], [x0 + d, y1], [x0, cy]]))];\n\t\t}\n\t\tcase 'diam':\n\t\tcase 'diamond':\n\t\tcase 'choice':\n\t\t\treturn [fill(poly([[cx, y0], [x1, cy], [cx, y1], [x0, cy]]))];\n\t\tcase 'hex':\n\t\tcase 'hexagon': {\n\t\t\tconst d = Math.min(W / 5, H / 2);\n\t\t\treturn [fill(poly([[x0 + d, y0], [x1 - d, y0], [x1, cy], [x1 - d, y1], [x0 + d, y1], [x0, cy]]))];\n\t\t}\n\t\tcase 'lean-r': {\n\t\t\tconst d = Math.min(W / 5, H / 2);\n\t\t\treturn [fill(poly([[x0 + d, y0], [x1, y0], [x1 - d, y1], [x0, y1]]))];\n\t\t}\n\t\tcase 'lean-l': {\n\t\t\tconst d = Math.min(W / 5, H / 2);\n\t\t\treturn [fill(poly([[x0, y0], [x1 - d, y0], [x1, y1], [x0 + d, y1]]))];\n\t\t}\n\t\tcase 'trap-b': {\n\t\t\tconst d = Math.min(W / 5, H / 2);\n\t\t\treturn [fill(poly([[x0 + d, y0], [x1 - d, y0], [x1, y1], [x0, y1]]))];\n\t\t}\n\t\tcase 'trap-t': {\n\t\t\tconst d = Math.min(W / 5, H / 2);\n\t\t\treturn [fill(poly([[x0, y0], [x1, y0], [x1 - d, y1], [x0 + d, y1]]))];\n\t\t}\n\t\tcase 'text':\n\t\tcase 'anchor':\n\t\t\treturn [{ d: rect(x0, y0, W, H), paint: 'hidden' }];\n\t\tcase 'card':\n\t\tcase 'notch-rect': {\n\t\t\tconst d = Math.min(14, H / 3);\n\t\t\treturn [fill(poly([[x0 + d, y0], [x1, y0], [x1, y1], [x0, y1], [x0, y0 + d]]))];\n\t\t}\n\t\tcase 'lin-rect':\n\t\tcase 'shaded-process':\n\t\t\treturn [fill(rect(x0, y0, W, H)), stroke(line(x0 + 8, y0, x0 + 8, y1))];\n\t\tcase 'fork':\n\t\tcase 'join':\n\t\t\treturn [solid(rect(x0, y0, W, H, 2))];\n\t\tcase 'hourglass':\n\t\t\treturn [fill(poly([[x0, y0], [x1, y0], [x0, y1], [x1, y1]]))];\n\t\tcase 'bolt':\n\t\t\treturn [fill(poly([[cx + W * 0.1, y0], [x0 + W * 0.25, cy + 4], [cx, cy + 4], [cx - W * 0.1, y1], [x1 - W * 0.25, cy - 4], [cx, cy - 4]]))];\n\t\tcase 'doc':\n\t\tcase 'lin-doc':\n\t\tcase 'tag-doc': {\n\t\t\tconst wave = Math.min(12, H / 5);\n\t\t\tconst paths = [fill(`M${n(x0)},${n(y0)} L${n(x1)},${n(y0)} L${n(x1)},${n(y1 - wave)} Q${n(x1 - W / 4)},${n(y1 - wave * 2.2)} ${n(cx)},${n(y1 - wave)} T${n(x0)},${n(y1 - wave)} Z`)];\n\t\t\tif (shape === 'lin-doc') paths.push(stroke(line(x0 + 8, y0, x0 + 8, y1 - wave)));\n\t\t\tif (shape === 'tag-doc') paths.push(stroke(line(x1 - 12, y1 - wave * 1.6, x1, y1 - wave * 2.6)));\n\t\t\treturn paths;\n\t\t}\n\t\tcase 'docs': {\n\t\t\tconst wave = Math.min(10, H / 6);\n\t\t\tconst page = (dx: number, dy: number, inner: number) =>\n\t\t\t\tfill(`M${n(x0 + dx)},${n(y0 + dy)} L${n(x1 - inner)},${n(y0 + dy)} L${n(x1 - inner)},${n(y1 - wave - (12 - dy))} Q${n(x1 - W / 4 - inner)},${n(y1 - wave * 2.2 - (12 - dy))} ${n(cx + 6 - dy)},${n(y1 - wave - (12 - dy))} T${n(x0 + dx)},${n(y1 - wave - (12 - dy))} Z`);\n\t\t\treturn [page(12, 0, 0), page(6, 6, 6), page(0, 12, 12)];\n\t\t}\n\t\tcase 'delay':\n\t\t\treturn [fill(`M${n(x0)},${n(y0)} L${n(x1 - H / 2)},${n(y0)} A${n(H / 2)},${n(H / 2)} 0 0 1 ${n(x1 - H / 2)},${n(y1)} L${n(x0)},${n(y1)} Z`)];\n\t\tcase 'curv-trap': {\n\t\t\tconst d = Math.min(W / 6, H / 3);\n\t\t\treturn [fill(`M${n(x0 + d)},${n(y0)} L${n(x1 - d)},${n(y0)} Q${n(x1)},${n(y0)} ${n(x1)},${n(cy)} Q${n(x1)},${n(y1)} ${n(x1 - d)},${n(y1)} L${n(x0 + d)},${n(y1)} L${n(x0)},${n(cy)} Z`)];\n\t\t}\n\t\tcase 'div-rect': {\n\t\t\tconst d = Math.min(24, H / 3);\n\t\t\treturn [fill(rect(x0, y0, W, H)), stroke(line(x0, y0 + d, x1, y0 + d))];\n\t\t}\n\t\tcase 'tri':\n\t\t\treturn [fill(poly([[cx, y0], [x1, y1], [x0, y1]]))];\n\t\tcase 'flip-tri':\n\t\t\treturn [fill(poly([[x0, y0], [x1, y0], [cx, y1]]))];\n\t\tcase 'win-pane':\n\t\t\treturn [fill(rect(x0, y0, W, H)), stroke(line(x0 + 10, y0, x0 + 10, y1)), stroke(line(x0, y0 + 10, x1, y0 + 10))];\n\t\tcase 'flag': {\n\t\t\tconst wave = Math.min(10, H / 5);\n\t\t\treturn [fill(`M${n(x0)},${n(y0 + wave)} Q${n(x0 + W / 4)},${n(y0 - wave)} ${n(cx)},${n(y0 + wave)} T${n(x1)},${n(y0 + wave)} L${n(x1)},${n(y1 - wave)} Q${n(x1 - W / 4)},${n(y1 + wave)} ${n(cx)},${n(y1 - wave)} T${n(x0)},${n(y1 - wave)} Z`)];\n\t\t}\n\t\tcase 'st-rect':\n\t\t\treturn [fill(rect(x0 + 8, y0, W - 8, H - 8)), fill(rect(x0 + 4, y0 + 4, W - 8, H - 8)), fill(rect(x0, y0 + 8, W - 8, H - 8))];\n\t\tcase 'bow-rect': {\n\t\t\tconst d = Math.min(W / 8, 14);\n\t\t\treturn [fill(`M${n(x0)},${n(y0)} Q${n(x0 + d)},${n(cy)} ${n(x0)},${n(y1)} L${n(x1)},${n(y1)} Q${n(x1 - d)},${n(cy)} ${n(x1)},${n(y0)} Z`)];\n\t\t}\n\t\tcase 'tag-rect':\n\t\t\treturn [fill(rect(x0, y0, W, H)), stroke(line(x1 - 14, y1, x1, y1 - 14))];\n\t\tcase 'notch-pent': {\n\t\t\tconst d = Math.min(W / 6, H / 3);\n\t\t\treturn [fill(poly([[x0 + d, y0], [x1 - d, y0], [x1, y0 + d], [x1, y1], [x0, y1], [x0, y0 + d]]))];\n\t\t}\n\t\tcase 'sl-rect': {\n\t\t\tconst d = Math.min(H / 3, 20);\n\t\t\treturn [fill(poly([[x0, y0 + d], [x1, y0], [x1, y1], [x0, y1]]))];\n\t\t}\n\t\tcase 'brace':\n\t\tcase 'brace-l':\n\t\tcase 'brace-r':\n\t\tcase 'braces': {\n\t\t\tconst left = `M${n(x0 + 18)},${n(y0)} Q${n(x0 + 6)},${n(y0)} ${n(x0 + 6)},${n(cy - 6)} Q${n(x0 + 6)},${n(cy)} ${n(x0)},${n(cy)} Q${n(x0 + 6)},${n(cy)} ${n(x0 + 6)},${n(cy + 6)} Q${n(x0 + 6)},${n(y1)} ${n(x0 + 18)},${n(y1)}`;\n\t\t\tconst right = `M${n(x1 - 18)},${n(y0)} Q${n(x1 - 6)},${n(y0)} ${n(x1 - 6)},${n(cy - 6)} Q${n(x1 - 6)},${n(cy)} ${n(x1)},${n(cy)} Q${n(x1 - 6)},${n(cy)} ${n(x1 - 6)},${n(cy + 6)} Q${n(x1 - 6)},${n(y1)} ${n(x1 - 18)},${n(y1)}`;\n\t\t\tconst hit = { d: rect(x0, y0, W, H), paint: 'hidden' as const };\n\t\t\tif (shape === 'brace-r') return [hit, stroke(right)];\n\t\t\tif (shape === 'braces') return [hit, stroke(left), stroke(right)];\n\t\t\treturn [hit, stroke(left)];\n\t\t}\n\t\tcase 'comment':\n\t\t\treturn [{ d: rect(x0, y0, W, H), paint: 'hidden' }, stroke(`M${n(x0 + 14)},${n(y0)} Q${n(x0)},${n(y0)} ${n(x0)},${n(y0 + 14)} L${n(x0)},${n(y1 - 14)} Q${n(x0)},${n(y1)} ${n(x0 + 14)},${n(y1)}`)];\n\t\tcase 'icon':\n\t\tcase 'image':\n\t\t\treturn [fill(rect(x0, y0, W, H, 6))];\n\t\tcase 'note':\n\t\t\treturn [fill(poly([[x0, y0], [x1 - 12, y0], [x1, y0 + 12], [x1, y1], [x0, y1]]))];\n\t\tdefault:\n\t\t\treturn [fill(rect(x0, y0, W, H))];\n\t}\n}\n\n/**\n * The arrow marker to draw for a Mermaid arrow head on a canvas edge: `arrowclosed` for\n * the filled heads (`-->`, `<|--`, ER cardinalities fall back to it), `arrow` for open\n * ones, or `null` for none. Canvas libraries map these to their own marker types.\n */\nexport function mermaidEdgeMarker(head: string): 'arrow' | 'arrowclosed' | null {\n\tif (head === 'none') return null;\n\tif (head === 'arrow_open' || head === 'dependency') return 'arrow';\n\treturn 'arrowclosed';\n}\n\n\n/**\n * The CSS custom properties and colours a Mermaid node paints with, from its resolved\n * `style` / `classDef` declarations. Unstyled nodes use the host's `--card`, `--foreground`\n * and `--primary` colours (with fallbacks), so they follow the app theme.\n */\nexport function mermaidNodePaint(styles: Record<string, string> = {}, selected = false): Record<string, string> {\n\tconst paint: Record<string, string> = {\n\t\t'--pixerate-diagram-node-bg': styles.fill ?? 'var(--card, var(--background, #ffffff))',\n\t\t'--pixerate-diagram-node-border': selected ? 'var(--primary, #2563eb)' : (styles.stroke ?? 'var(--foreground, #0f172a)'),\n\t\tcolor: styles.color ?? 'var(--foreground, #0f172a)',\n\t\t'stroke-width': styles['stroke-width'] ?? (selected ? '2px' : '1.25px')\n\t};\n\tif (styles['stroke-dasharray']) paint['stroke-dasharray'] = styles['stroke-dasharray'];\n\treturn paint;\n}\n\n/** How each {@link MermaidShapePaint} fills and strokes, as CSS declarations. */\nexport const MERMAID_SHAPE_PAINT: Record<MermaidShapePaint, { fill: string; stroke: string }> = {\n\tshape: { fill: 'var(--pixerate-diagram-node-bg)', stroke: 'var(--pixerate-diagram-node-border)' },\n\tline: { fill: 'none', stroke: 'var(--pixerate-diagram-node-border)' },\n\tfilled: { fill: 'var(--pixerate-diagram-node-border)', stroke: 'var(--pixerate-diagram-node-border)' },\n\thidden: { fill: 'transparent', stroke: 'none' }\n};\n\n/** A Mermaid label as plain lines: `<br>` splits lines, other tags and markdown-string quotes are dropped. */\nexport function mermaidLabelLines(label: string): string[] {\n\treturn String(label)\n\t\t.replace(/^\"`?|`?\"$/g, '')\n\t\t.split(/<br\\s*\\/?>|\\n/i)\n\t\t.map((line) => line.replace(/<[^>]+>/g, ''));\n}\n\n/** Class members / ER attributes of a node, one display line each. */\nexport function mermaidNodeMembers(data: Record<string, unknown> | undefined): string[] {\n\tconst list = [...((data?.members as Array<{ raw?: string }>) ?? []), ...((data?.attributes as Array<{ raw?: string }>) ?? [])];\n\treturn list.map((m) => m.raw ?? '');\n}\n","// Thumbnails and PNG export follow Ursula's MermaidThumbnail and src/react/export.ts\n// (`downloadCanvasPng`, a longest-side bound on snapshots), drawn from the layout as standalone\n// SVG instead of capturing the DOM, so they work without the canvas mounted.\nimport type { DiagramGraph, MermaidDocument } from '../../diagram/types.js';\nimport { ensureDiagramLayout, type DiagramLayout, type DiagramLayoutOptions } from './layout.js';\nimport { mermaidEdgeMarker, mermaidLabelLines, mermaidNodeMembers, mermaidShapePaths } from './shapes.js';\nimport { mermaidEdgeDash, resolveMermaidStyles } from './toFlow.js';\nimport { DIAGRAM_SVG_LIGHT_THEME } from './theme.js';\nimport { DIAGRAM_SCENE_LIGHT_THEME, diagramMarksToSvg, familySceneFromLayout, type DiagramSceneTheme } from './scene.js';\n\nexport { DIAGRAM_SVG_DARK_THEME, DIAGRAM_SVG_LIGHT_THEME, type DiagramSvgTheme } from './theme.js';\n\nexport interface DiagramSvgOptions extends DiagramLayoutOptions {\n\t/** A layout to draw instead of computing one (e.g. the canvas' current layout). */\n\tlayout?: DiagramLayout;\n\t/** Colours; the scene colours (`palette`, `grid`…) are used by families drawn as scenes. */\n\ttheme?: Partial<DiagramSceneTheme>;\n\t/** Room around the diagram in px (default 16). */\n\tpadding?: number;\n\t/**\n\t * Fit inside this box (a thumbnail): the SVG's `width` / `height` shrink to fit, keeping the\n\t * aspect ratio; the drawing is not enlarged. Default: natural size.\n\t */\n\tmaxWidth?: number;\n\tmaxHeight?: number;\n\t/** Accessible name (`<title>`); default the diagram's title or family. */\n\ttitle?: string;\n\t/** Leave out the background rectangle (transparent). */\n\ttransparent?: boolean;\n}\n\nexport interface DiagramSvg {\n\tsvg: string;\n\t/** The SVG's `width` / `height` attributes (after fitting). */\n\twidth: number;\n\theight: number;\n\t/** Natural size of the drawing (before fitting), in px. */\n\tnaturalWidth: number;\n\tnaturalHeight: number;\n}\n\nconst esc = (text: string) => text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\nconst n = (v: number) => String(Math.round(v * 100) / 100);\n\ninterface Box {\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n}\n\n/** Where the segment from the box's centre towards `to` leaves the box. */\nfunction exitPoint(box: Box, to: { x: number; y: number }): { x: number; y: number } {\n\tconst cx = box.x + box.width / 2;\n\tconst cy = box.y + box.height / 2;\n\tconst dx = to.x - cx;\n\tconst dy = to.y - cy;\n\tif (dx === 0 && dy === 0) return { x: cx, y: cy };\n\tconst sx = dx === 0 ? Infinity : box.width / 2 / Math.abs(dx);\n\tconst sy = dy === 0 ? Infinity : box.height / 2 / Math.abs(dy);\n\tconst t = Math.min(sx, sy);\n\treturn { x: cx + dx * t, y: cy + dy * t };\n}\n\n/**\n * Draws a Mermaid diagram as a standalone SVG. Flowchart, state, class and ER diagrams: groups,\n * nodes in their Mermaid shapes with `style` / `classDef` colours, labels, class members and\n * ER attributes, and edges with labels, line patterns and arrow heads. Families with their own\n * picture (pie, Gantt, sequence, XY, radar…) are drawn from their scene (`mermaidToScene`).\n * Uses stored `%% @pixerate` positions (auto-layout for the rest), so it matches the canvas.\n * Pure: no DOM.\n */\nexport function mermaidToSvg(doc: MermaidDocument, options: DiagramSvgOptions = {}): DiagramSvg {\n\tconst theme = { ...DIAGRAM_SVG_LIGHT_THEME, ...options.theme };\n\tconst pad = options.padding ?? 16;\n\tconst graph: DiagramGraph = doc.graph;\n\tconst layout = options.layout ?? ensureDiagramLayout(graph, doc.meta, { family: doc.family, ...options });\n\tconst boxOf = (id: string): Box | null => {\n\t\tconst p = layout.positions[id];\n\t\tif (!p) return null;\n\t\tconst s = layout.sizes[id] ?? { width: 160, height: 56 };\n\t\treturn { x: p[0], y: p[1], width: s.width, height: s.height };\n\t};\n\tconst scene = doc.tier === 'editable' ? familySceneFromLayout(doc.family, graph, layout, { theme: { ...DIAGRAM_SCENE_LIGHT_THEME, ...theme } }) : null;\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tif (scene && scene.marks.length) {\n\t\tminX = scene.x;\n\t\tminY = scene.y;\n\t\tmaxX = scene.x + scene.width;\n\t\tmaxY = scene.y + scene.height;\n\t} else for (const id of Object.keys(layout.positions)) {\n\t\tconst b = boxOf(id)!;\n\t\tminX = Math.min(minX, b.x);\n\t\tminY = Math.min(minY, b.y);\n\t\tmaxX = Math.max(maxX, b.x + b.width);\n\t\tmaxY = Math.max(maxY, b.y + b.height);\n\t}\n\tif (minX === Infinity) minX = minY = maxX = maxY = 0;\n\tconst naturalWidth = Math.max(1, Math.ceil(maxX - minX + pad * 2));\n\tconst naturalHeight = Math.max(1, Math.ceil(maxY - minY + pad * 2));\n\tconst fit = Math.min(1, (options.maxWidth ?? Infinity) / naturalWidth, (options.maxHeight ?? Infinity) / naturalHeight);\n\tconst width = Math.max(1, Math.round(naturalWidth * fit));\n\tconst height = Math.max(1, Math.round(naturalHeight * fit));\n\tconst ox = pad - minX;\n\tconst oy = pad - minY;\n\tconst fs = theme.fontSize;\n\tconst title = options.title ?? graph.title ?? `${doc.family} diagram`;\n\tconst out: string[] = [];\n\tout.push(\n\t\t`<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${naturalWidth} ${naturalHeight}\" role=\"img\" aria-label=\"${esc(title)}\" font-family=\"${esc(theme.fontFamily)}\" font-size=\"${fs}\">`,\n\t\t`<title>${esc(title)}</title>`,\n\t\t'<defs>',\n\t\t`<marker id=\"pd-arrow-closed\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"8\" markerHeight=\"8\" orient=\"auto-start-reverse\"><path d=\"M0,0 L10,5 L0,10 Z\" fill=\"${esc(theme.edge)}\"/></marker>`,\n\t\t`<marker id=\"pd-arrow-open\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"8\" markerHeight=\"8\" orient=\"auto-start-reverse\"><path d=\"M0,0 L10,5 L0,10\" fill=\"none\" stroke=\"${esc(theme.edge)}\" stroke-width=\"1.5\"/></marker>`,\n\t\t'</defs>'\n\t);\n\tif (!options.transparent) out.push(`<rect width=\"${naturalWidth}\" height=\"${naturalHeight}\" fill=\"${esc(theme.background)}\"/>`);\n\tout.push(`<g transform=\"translate(${n(ox)} ${n(oy)})\">`);\n\tif (scene) {\n\t\tout.push(diagramMarksToSvg(scene.marks, { ...DIAGRAM_SCENE_LIGHT_THEME, ...theme }), '</g></svg>');\n\t\treturn { svg: out.join(''), width, height, naturalWidth, naturalHeight };\n\t}\n\n\t// Groups, outermost first.\n\tconst depth = (id: string) => {\n\t\tlet d = 0;\n\t\tlet cur = graph.groups.find((g) => g.id === id)?.parentId;\n\t\twhile (cur && d < 50) {\n\t\t\td++;\n\t\t\tcur = graph.groups.find((g) => g.id === cur)?.parentId;\n\t\t}\n\t\treturn d;\n\t};\n\tfor (const g of [...graph.groups].sort((a, b) => depth(a.id) - depth(b.id))) {\n\t\tconst b = boxOf(g.id);\n\t\tif (!b) continue;\n\t\tconst st = resolveMermaidStyles(g.classes, g.styles, graph.classDefs);\n\t\tout.push(\n\t\t\t`<g data-group=\"${esc(g.id)}\"><rect x=\"${n(b.x)}\" y=\"${n(b.y)}\" width=\"${n(b.width)}\" height=\"${n(b.height)}\" rx=\"8\" fill=\"${esc(st.fill ?? theme.groupFill)}\" stroke=\"${esc(st.stroke ?? theme.groupStroke)}\" stroke-dasharray=\"4 3\"/>`,\n\t\t\t`<text x=\"${n(b.x + 10)}\" y=\"${n(b.y + 18)}\" fill=\"${esc(st.color ?? theme.groupText)}\" font-size=\"${fs - 1}\" font-weight=\"600\">${esc(mermaidLabelLines(g.label).join(' '))}</text></g>`\n\t\t);\n\t}\n\n\t// Edges under nodes.\n\tfor (const e of graph.edges) {\n\t\tconst a = boxOf(e.source);\n\t\tconst b = boxOf(e.target);\n\t\tif (!a || !b || e.pattern === 'invisible') continue;\n\t\tconst ca = { x: a.x + a.width / 2, y: a.y + a.height / 2 };\n\t\tconst cb = { x: b.x + b.width / 2, y: b.y + b.height / 2 };\n\t\tconst p = exitPoint(a, cb);\n\t\tconst q = exitPoint(b, ca);\n\t\tconst st = resolveMermaidStyles(e.classes, e.styles, graph.classDefs);\n\t\tconst end = mermaidEdgeMarker(e.arrowEnd);\n\t\tconst start = mermaidEdgeMarker(e.arrowStart);\n\t\tconst marker = (m: string | null) => (m === 'arrow' ? 'url(#pd-arrow-open)' : m ? 'url(#pd-arrow-closed)' : '');\n\t\tconst dash = mermaidEdgeDash(e.pattern);\n\t\tout.push(\n\t\t\t`<line data-edge=\"${esc(e.id)}\" x1=\"${n(p.x)}\" y1=\"${n(p.y)}\" x2=\"${n(q.x)}\" y2=\"${n(q.y)}\" stroke=\"${esc(st.stroke ?? theme.edge)}\" stroke-width=\"${e.pattern === 'thick' ? 3 : 1.5}\"${dash ? ` stroke-dasharray=\"${dash}\"` : ''}${end ? ` marker-end=\"${marker(end)}\"` : ''}${start ? ` marker-start=\"${marker(start)}\"` : ''}/>`\n\t\t);\n\t\tif (e.label) {\n\t\t\tconst text = mermaidLabelLines(e.label).join(' ');\n\t\t\tconst w = text.length * fs * 0.55 + 10;\n\t\t\tconst mx = (p.x + q.x) / 2;\n\t\t\tconst my = (p.y + q.y) / 2;\n\t\t\tout.push(\n\t\t\t\t`<rect x=\"${n(mx - w / 2)}\" y=\"${n(my - fs * 0.75)}\" width=\"${n(w)}\" height=\"${n(fs * 1.5)}\" rx=\"3\" fill=\"${esc(theme.edgeLabelBackground)}\"/>`,\n\t\t\t\t`<text x=\"${n(mx)}\" y=\"${n(my)}\" text-anchor=\"middle\" dominant-baseline=\"central\" fill=\"${esc(st.color ?? theme.text)}\" font-size=\"${fs - 1}\">${esc(text)}</text>`\n\t\t\t);\n\t\t}\n\t}\n\n\t// Nodes.\n\tfor (const node of graph.nodes) {\n\t\tconst b = boxOf(node.id);\n\t\tif (!b) continue;\n\t\tconst st = resolveMermaidStyles(node.classes, node.styles, graph.classDefs);\n\t\tconst fill = st.fill ?? theme.nodeFill;\n\t\tconst stroke = st.stroke ?? theme.nodeStroke;\n\t\tconst color = st.color ?? theme.text;\n\t\tconst strokeWidth = st['stroke-width']?.replace('px', '') ?? '1.25';\n\t\tconst paint = (kind: string) =>\n\t\t\tkind === 'shape'\n\t\t\t\t? `fill=\"${esc(fill)}\" stroke=\"${esc(stroke)}\" stroke-width=\"${esc(strokeWidth)}\"`\n\t\t\t\t: kind === 'line'\n\t\t\t\t\t? `fill=\"none\" stroke=\"${esc(stroke)}\" stroke-width=\"${esc(strokeWidth)}\"`\n\t\t\t\t\t: kind === 'filled'\n\t\t\t\t\t\t? `fill=\"${esc(stroke)}\" stroke=\"${esc(stroke)}\"`\n\t\t\t\t\t\t: 'fill=\"none\" stroke=\"none\"';\n\t\tout.push(`<g data-node=\"${esc(node.id)}\" transform=\"translate(${n(b.x)} ${n(b.y)})\">`);\n\t\tfor (const path of mermaidShapePaths(node.shape, b.width, b.height)) out.push(`<path d=\"${path.d}\" ${paint(path.paint)}/>`);\n\t\tconst lines = mermaidLabelLines(node.label).filter((l, i, all) => l || all.length === 1);\n\t\tconst members = mermaidNodeMembers(node.data);\n\t\tconst lh = fs * 1.3;\n\t\tif (members.length) {\n\t\t\tlines.forEach((line, i) => out.push(`<text x=\"${n(b.width / 2)}\" y=\"${n(14 + lh * i)}\" text-anchor=\"middle\" dominant-baseline=\"central\" fill=\"${esc(color)}\" font-weight=\"600\">${esc(line)}</text>`));\n\t\t\tconst top = 14 + lh * lines.length - lh / 2 + 4;\n\t\t\tout.push(`<line x1=\"0\" y1=\"${n(top)}\" x2=\"${n(b.width)}\" y2=\"${n(top)}\" stroke=\"${esc(stroke)}\" stroke-width=\"1\"/>`);\n\t\t\tmembers.forEach((m, i) => out.push(`<text x=\"8\" y=\"${n(top + 4 + lh * (i + 0.5))}\" dominant-baseline=\"central\" fill=\"${esc(color)}\" font-size=\"${fs - 1}\">${esc(m)}</text>`));\n\t\t} else {\n\t\t\tconst first = b.height / 2 - ((lines.length - 1) * lh) / 2;\n\t\t\tlines.forEach((line, i) => out.push(`<text x=\"${n(b.width / 2)}\" y=\"${n(first + lh * i)}\" text-anchor=\"middle\" dominant-baseline=\"central\" fill=\"${esc(color)}\">${esc(line)}</text>`));\n\t\t}\n\t\tout.push('</g>');\n\t}\n\tout.push('</g></svg>');\n\treturn { svg: out.join(''), width, height, naturalWidth, naturalHeight };\n}\n\n/** A small preview of a diagram: `mermaidToSvg` fitted inside `maxWidth` × `maxHeight` (default 240 × 160). */\nexport function mermaidThumbnailSvg(doc: MermaidDocument, options: DiagramSvgOptions = {}): DiagramSvg {\n\treturn mermaidToSvg(doc, { maxWidth: 240, maxHeight: 160, padding: 8, ...options });\n}\n\n/** Longest side, in pixels, of a rasterized diagram unless `maxSize` says otherwise. */\nexport const DIAGRAM_RASTER_MAX_SIZE = 2048;\n\nexport interface RasterizeSvgOptions {\n\t/** Size of the SVG in CSS pixels. */\n\twidth: number;\n\theight: number;\n\t/** Pixels per CSS pixel (default 2), reduced so the longest side stays within `maxSize`. */\n\tscale?: number;\n\t/** Longest side of the image in pixels (default 2048). */\n\tmaxSize?: number;\n\t/** Fill behind the drawing (default white; `transparent` keeps the alpha channel). */\n\tbackground?: string;\n\t/** Document to create the canvas and image in (default: the global one). */\n\tdocument?: Document;\n}\n\nexport interface RasterImage {\n\t/** Base64 PNG, without the `data:` prefix. */\n\tdata: string;\n\tmimeType: 'image/png';\n\twidth: number;\n\theight: number;\n}\n\n/** Pixel size of the image for an SVG of `width` × `height`: `scale` × the size, longest side ≤ `maxSize`. */\nexport function rasterSize(width: number, height: number, scale = 2, maxSize = DIAGRAM_RASTER_MAX_SIZE): { width: number; height: number; scale: number } {\n\tif (!(width > 0) || !(height > 0)) throw new Error(`An image needs a positive width and height; got ${width} × ${height}.`);\n\tconst factor = Math.min(scale > 0 ? scale : 1, maxSize / Math.max(width, height));\n\treturn { width: Math.max(1, Math.round(width * factor)), height: Math.max(1, Math.round(height * factor)), scale: factor };\n}\n\nconst NO_DOM =\n\t'Rasterizing needs a browser DOM (document, Image and a 2D canvas). Outside a browser, use the SVG (mermaidToSvg, or render_diagram with format \"svg\") with an SVG renderer.';\n\n/**\n * Turns a standalone SVG (e.g. `mermaidToSvg(...).svg`) into a PNG in the browser: draws it\n * through an `Image` onto a canvas at `scale` (longest side ≤ `maxSize`) over `background`.\n * The SVG must use concrete colours (CSS variables do not apply inside an image). Throws a\n * sentence saying what to do instead when there is no DOM.\n */\nexport async function rasterizeSvg(svg: string, options: RasterizeSvgOptions): Promise<RasterImage> {\n\tconst doc = options.document ?? (globalThis as { document?: Document }).document;\n\tconst ImageCtor = (doc?.defaultView as (Window & typeof globalThis) | null | undefined)?.Image ?? (globalThis as { Image?: typeof Image }).Image;\n\tif (!doc || typeof doc.createElement !== 'function' || !ImageCtor) throw new Error(NO_DOM);\n\tconst size = rasterSize(options.width, options.height, options.scale, options.maxSize);\n\tconst canvas = doc.createElement('canvas');\n\tcanvas.width = size.width;\n\tcanvas.height = size.height;\n\tlet context: CanvasRenderingContext2D | null = null;\n\ttry {\n\t\tcontext = canvas.getContext('2d');\n\t} catch {\n\t\tcontext = null;\n\t}\n\tif (!context) throw new Error(NO_DOM);\n\tconst image = new ImageCtor();\n\tawait new Promise<void>((resolve, reject) => {\n\t\timage.onload = () => resolve();\n\t\timage.onerror = () => reject(new Error('The SVG could not be loaded as an image; check that it is a complete <svg> document with the SVG namespace.'));\n\t\timage.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;\n\t});\n\tconst background = options.background ?? '#ffffff';\n\tif (background !== 'transparent') {\n\t\tcontext.fillStyle = background;\n\t\tcontext.fillRect(0, 0, size.width, size.height);\n\t}\n\tcontext.drawImage(image, 0, 0, size.width, size.height);\n\tconst url = canvas.toDataURL('image/png');\n\tif (!url.startsWith('data:image/png')) throw new Error('This canvas cannot export PNG (toDataURL returned no image).');\n\treturn { data: url.slice(url.indexOf(',') + 1), mimeType: 'image/png', width: size.width, height: size.height };\n}\n\n/** A file name for a diagram: its title in lower case with dashes, else its family, plus the extension. */\nexport function diagramFileName(doc: Pick<MermaidDocument, 'family' | 'graph'>, extension: string): string {\n\tconst base = (doc.graph.title ?? '')\n\t\t.toLowerCase()\n\t\t.replace(/[^\\p{L}\\p{N}]+/gu, '-')\n\t\t.replace(/^-+|-+$/g, '')\n\t\t.slice(0, 60);\n\treturn `${base || `${doc.family}-diagram`}.${extension}`;\n}\n\nexport interface DownloadDiagramPngOptions extends DiagramSvgOptions, Omit<RasterizeSvgOptions, 'width' | 'height'> {\n\t/** File name (default: the diagram title, else `<family>-diagram`, plus `.png`). */\n\tfilename?: string;\n}\n\n/** Renders a diagram to PNG and saves it through a download link. Returns the image and file name. */\nexport async function downloadDiagramPng(doc: MermaidDocument, options: DownloadDiagramPngOptions = {}): Promise<RasterImage & { filename: string }> {\n\tconst drawn = mermaidToSvg(doc, options);\n\tconst background = options.background ?? (options.transparent ? 'transparent' : (options.theme?.background ?? DIAGRAM_SVG_LIGHT_THEME.background));\n\tconst image = await rasterizeSvg(drawn.svg, { ...options, width: drawn.width, height: drawn.height, background });\n\tconst filename = options.filename ?? diagramFileName(doc, 'png');\n\tconst page = options.document ?? document;\n\tconst link = page.createElement('a');\n\tlink.href = `data:image/png;base64,${image.data}`;\n\tlink.download = filename;\n\tpage.body.appendChild(link);\n\tlink.click();\n\tlink.remove();\n\treturn { ...image, filename };\n}\n","/**\n * Mermaid diagrams on the canvas: lays out a `MermaidDocument` (dagre, nested groups,\n * stored `%% @pixerate` positions blended with auto-layout for new nodes), projects it\n * onto canvas nodes and edges, maps canvas drags back to `moveNodes` edits, and streams\n * diagrams that are still being written. Ported from Ursula.\n */\nimport type { EditOp, MermaidDocument, Point } from '../../diagram/types.js';\nimport { ensureDiagramLayout, type DiagramLayout, type DiagramLayoutOptions } from './layout.js';\nimport { mermaidFlowEdges, mermaidFlowNodes, type MermaidFlowEdge, type MermaidFlowNode } from './toFlow.js';\n\nexport * from './layout.js';\nexport * from './toFlow.js';\nexport * from './view.js';\nexport * from './stream.js';\nexport * from './scene.js';\nexport * from './families/index.js';\n\nexport interface MermaidCanvasOptions extends DiagramLayoutOptions {\n\t/** Nodes can be dragged, connected and selected (default false). */\n\teditable?: boolean;\n\t/** A layout to draw instead of computing one (e.g. a tween frame). */\n\tlayout?: DiagramLayout;\n}\n\nexport interface MermaidCanvas {\n\tnodes: MermaidFlowNode[];\n\tedges: MermaidFlowEdge[];\n\t/** Absolute positions and sizes of every node and group. */\n\tlayout: DiagramLayout;\n}\n\n/**\n * Lays out a Mermaid document and returns canvas nodes (groups first, child positions\n * relative to their parent as React Flow / Svelte Flow expect) and edges.\n */\nexport function mermaidToCanvas(doc: MermaidDocument, options: MermaidCanvasOptions = {}): MermaidCanvas {\n\tconst layout = options.layout ?? ensureDiagramLayout(doc.graph, doc.meta, { family: doc.family, ...options });\n\treturn {\n\t\tnodes: mermaidFlowNodes(doc.graph, layout, { editable: options.editable }),\n\t\tedges: mermaidFlowEdges(doc.graph, { editable: options.editable }),\n\t\tlayout\n\t};\n}\n\nexport interface MermaidMovesOptions {\n\t/**\n\t * Positions already stored in the document (`doc.meta.layout`). Nodes that are laid out\n\t * automatically and not yet stored are pinned where they are, so moving one node does\n\t * not shift the others (new nodes are blended in around stored ones).\n\t */\n\tstored?: Readonly<Record<string, Point>>;\n}\n\n/**\n * The `moveNodes` edit for canvas nodes that were dragged: converts their positions\n * (relative to their parent) back to absolute coordinates. Returns `null` when nothing\n * moved by at least half a pixel.\n */\nexport function mermaidMovesFromCanvas(\n\tlayout: DiagramLayout,\n\tmoved: ReadonlyArray<{ id: string; position: { x: number; y: number }; parentId?: string }>,\n\tall: ReadonlyArray<{ id: string; position: { x: number; y: number }; parentId?: string }> = moved,\n\toptions: MermaidMovesOptions = {}\n): Extract<EditOp, { type: 'moveNodes' }> | null {\n\t// The moved nodes win over their stale copies in `all`.\n\tconst byId = new Map([...all, ...moved].map((n) => [n.id, n]));\n\tconst absolute = (id: string, seen = new Set<string>()): Point => {\n\t\tconst node = byId.get(id);\n\t\tif (!node) return layout.positions[id] ?? [0, 0];\n\t\tif (!node.parentId || seen.has(id)) return [node.position.x, node.position.y];\n\t\tseen.add(id);\n\t\tconst [px, py] = absolute(node.parentId, seen);\n\t\treturn [px + node.position.x, py + node.position.y];\n\t};\n\tconst moves = moved\n\t\t.map((n) => ({ id: n.id, position: absolute(n.id) }))\n\t\t.filter(({ id, position: [x, y] }) => {\n\t\t\tconst before = layout.positions[id];\n\t\t\treturn !before || Math.abs(before[0] - x) >= 0.5 || Math.abs(before[1] - y) >= 0.5;\n\t\t});\n\t// A dragged group carries its contents, so store their new absolute positions too.\n\tconst movedIds = new Set(moves.map((m) => m.id));\n\tfor (const n of all) {\n\t\tif (movedIds.has(n.id)) continue;\n\t\tlet parent = n.parentId;\n\t\twhile (parent && !movedIds.has(parent)) parent = byId.get(parent)?.parentId;\n\t\tif (parent) moves.push({ id: n.id, position: absolute(n.id) });\n\t}\n\tif (!moves.length) return null;\n\tif (options.stored) {\n\t\tconst taken = new Set(moves.map((m) => m.id));\n\t\tfor (const [id, position] of Object.entries(layout.positions)) {\n\t\t\tif (!taken.has(id) && !options.stored[id]) moves.push({ id, position });\n\t\t}\n\t}\n\treturn { type: 'moveNodes', moves };\n}\nexport * from './shapes.js';\nexport * from './export.js';\n","/**\n * UI-agnostic agent presence trajectory, inspection anchor, and cursor rotation math.\n */\n\nexport const DEFAULT_CURSOR_INACTIVITY_MS = 12000;\n\nexport interface CanvasAgentCursor {\n\tid: string;\n\tuserId?: string;\n\tx: number;\n\ty: number;\n\tusername?: string;\n\tavatarUrl?: string;\n\tcolor?: string;\n\tisAgent?: boolean;\n\tisServiceAccount?: boolean;\n\trole?: string;\n\tstatusText?: string;\n\tmessage?: string;\n\tnote?: string;\n\tlastUpdate?: number;\n\ttimeoutMs?: number;\n\ttransitionDurationMs?: number;\n\tcustomEase?: string;\n\ttargetNodeId?: string;\n\ttargetNodeCenter?: { x: number; y: number } | null;\n\tisModalOpen?: boolean;\n\tisClicking?: boolean;\n}\n\n/**\n * Deterministic string hash function used to derive consistent per-agent / per-node variances.\n */\nexport function hashString(str: string): number {\n\tlet hash = 0;\n\tfor (let i = 0; i < str.length; i++) {\n\t\thash = (hash << 5) - hash + str.charCodeAt(i);\n\t\thash |= 0;\n\t}\n\treturn Math.abs(hash);\n}\n\nexport type CardInspectionZone = 'header' | 'action' | 'body' | 'footer';\n\nexport const CARD_INSPECTION_ZONES: Record<CardInspectionZone, { x: number; y: number }> = {\n\theader: { x: 36, y: 24 },\n\taction: { x: 180, y: 28 },\n\tbody: { x: 60, y: 84 },\n\tfooter: { x: 165, y: 122 }\n};\n\n/**\n * Computes a varied inspection anchor position on a node/card (default width ~250px, height ~160px).\n * Generates distinct natural resting spots so agents do not appear at the exact same coordinates.\n */\nexport function getAgentCardAnchor(\n\tagentId: string = 'agent',\n\tnodeId: string = 'node',\n\tzone: CardInspectionZone | number = 0,\n\toptions?: { randomJitter?: boolean; width?: number; height?: number }\n): { x: number; y: number; zoneName: CardInspectionZone } {\n\tconst zoneNames: CardInspectionZone[] = ['header', 'action', 'body', 'footer'];\n\tconst zoneName =\n\t\ttypeof zone === 'string' ? zone : zoneNames[Math.abs(Math.floor(zone)) % zoneNames.length];\n\n\tconst base = CARD_INSPECTION_ZONES[zoneName];\n\tconst seed = hashString(`${agentId}:${nodeId}:${zoneName}`);\n\n\t// Deterministic pseudo-random base offset within zone (-8px to +8px in X, -4px to +4px in Y)\n\tconst offsetX = (seed % 17) - 8;\n\tconst offsetY = ((seed >> 4) % 9) - 4;\n\n\t// Organic micro-randomness (live jitter ±12px in X, ±8px in Y when opted in)\n\tconst jitterX = options?.randomJitter ? (Math.random() - 0.5) * 24 : 0;\n\tconst jitterY = options?.randomJitter ? (Math.random() - 0.5) * 16 : 0;\n\n\tconst maxX = options?.width ? Math.max(24, options.width - 20) : 230;\n\tconst maxY = options?.height ? Math.max(20, options.height - 15) : 145;\n\n\treturn {\n\t\tx: Math.max(16, Math.min(maxX, Math.round(base.x + offsetX + jitterX))),\n\t\ty: Math.max(14, Math.min(maxY, Math.round(base.y + offsetY + jitterY))),\n\t\tzoneName\n\t};\n}\n\nexport interface CurvedPathOptions {\n\tarcHeight?: number; // negative lifts upward in screen coordinates, e.g. -30px\n\twobble?: number; // subtle lateral sway, e.g. 3px\n}\n\n/**\n * Calculates coordinates along a natural curved trajectory with parabolic lift\n * and optional lateral sway, replacing robotic straight linear movement.\n */\nexport function calculateCurvedPath(\n\tfromX: number,\n\tfromY: number,\n\ttoX: number,\n\ttoY: number,\n\tprogress: number,\n\toptions?: CurvedPathOptions\n): { x: number; y: number } {\n\tconst p = Math.max(0, Math.min(1, progress));\n\tconst arcHeight = options?.arcHeight ?? -30;\n\tconst wobble = options?.wobble ?? 0;\n\n\t// Parabolic lift peak at mid-flight (p = 0.5)\n\tconst arc = Math.sin(p * Math.PI) * arcHeight;\n\t// Natural full-cycle lateral sway\n\tconst sway = Math.sin(p * Math.PI * 2) * wobble;\n\n\tconst currentX = fromX + (toX - fromX) * p + sway;\n\tconst currentY = fromY + (toY - fromY) * p + arc;\n\n\treturn { x: currentX, y: currentY };\n}\n\n/**\n * Computes natural drag movement timing, parabolic arc amplitude, and grip offset\n * based on distance traveled and a deterministic seed.\n */\nexport function getNaturalMovementTiming(\n\tdistance: number,\n\tseedStr: string = 'drag'\n): {\n\tduration: number;\n\tarcHeight: number;\n\tgrabOffset: { x: number; y: number };\n} {\n\tconst seed = hashString(seedStr);\n\tconst absDist = Math.abs(distance);\n\n\t// Scale duration with distance: clamped between 500ms and 1000ms\n\tconst distDuration = 560 + Math.min(340, (absDist / 1000) * 300);\n\tconst variance = (seed % 120) - 60; // -60ms to +60ms\n\tconst duration = Math.round(Math.max(500, Math.min(1000, distDuration + variance)));\n\n\t// Arc height: larger distance lifts slightly higher (-24px to -44px)\n\tconst baseArc = -24 - Math.min(16, (absDist / 1000) * 14);\n\tconst arcVariance = ((seed >> 3) % 9) - 4; // -4px to +4px\n\tconst arcHeight = Math.round(baseArc + arcVariance);\n\n\t// Varied grab offset across the card header\n\tconst grabX = 24 + (seed % 24);\n\tconst grabY = 12 + ((seed >> 2) % 14);\n\n\treturn {\n\t\tduration,\n\t\tarcHeight,\n\t\tgrabOffset: { x: grabX, y: grabY }\n\t};\n}\n\n/**\n * Computes variable CSS transition duration in ms based on distance and seed.\n */\nexport function getCursorTransitionDuration(\n\tdistance: number,\n\tseedStr: string = 'cursor',\n\toptions?: { naturalVariance?: boolean }\n): number {\n\tconst seed = hashString(seedStr);\n\tconst jitter = (seed % 60) - 30; // -30ms to +30ms\n\t// Natural timing variance (±15% when opted in)\n\tconst variance = options?.naturalVariance ? 0.85 + Math.random() * 0.3 : 1;\n\n\tlet baseDuration = 560;\n\tif (distance < 60) {\n\t\tbaseDuration = 340;\n\t} else if (distance < 300) {\n\t\tbaseDuration = 440;\n\t}\n\tconst computed = Math.round((baseDuration + jitter) * variance);\n\treturn Math.max(300, Math.min(650, computed));\n}\n\n/**\n * Calculates the SVG rotation angle for aligning a presence cursor arrow\n * toward the center of a target node.\n */\nexport function calculateCursorArrowRotation(\n\tcursor: { x: number; y: number; isModalOpen?: boolean },\n\ttargetCenter?: { x: number; y: number } | null,\n\tisAgentOrServiceAccount: boolean = false,\n\tisModalOpen?: boolean,\n\tcurrentRotation: number = 0\n): { shouldPoint: boolean; rotationDeg: number } {\n\tconst hasModal = isModalOpen ?? Boolean(cursor.isModalOpen);\n\tconst shouldPoint = Boolean(targetCenter && (isAgentOrServiceAccount || hasModal));\n\n\tif (!shouldPoint || !targetCenter) {\n\t\treturn { shouldPoint: false, rotationDeg: 0 };\n\t}\n\n\tconst dx = targetCenter.x - cursor.x;\n\tconst dy = targetCenter.y - cursor.y;\n\tconst dist = Math.hypot(dx, dy);\n\n\tif (dist < 2) {\n\t\treturn { shouldPoint: true, rotationDeg: currentRotation };\n\t}\n\n\tconst angleDeg = Math.atan2(dy, dx) * (180 / Math.PI);\n\t// Default unrotated SVG pointer tip faces -135deg (up-and-left).\n\t// Rotating by (angleDeg + 135) directs the tip directly toward the target.\n\tconst rawTargetDeg = angleDeg + 135;\n\n\t// Shortest rotation unwrapping\n\tconst diff = ((((rawTargetDeg - currentRotation + 180) % 360) + 360) % 360) - 180;\n\treturn {\n\t\tshouldPoint: true,\n\t\trotationDeg: currentRotation + diff\n\t};\n}\n\n/**\n * Sanitizes an agent name by removing any leading '@' prefix(es) and trimming whitespace.\n */\nexport function formatAgentCursorName(nameOrUsername?: string | null): string {\n\tif (!nameOrUsername) return 'Agent';\n\tconst cleaned = nameOrUsername.trim().replace(/^@+/, '').trim();\n\treturn cleaned || 'Agent';\n}\n\n/**\n * Determines whether a given presence cursor represents an autonomous agent.\n */\nexport function isAgentCursor(cursor: Partial<CanvasAgentCursor>): boolean {\n\tif (cursor.isAgent) return true;\n\tif (cursor.userId?.startsWith('agent_') || cursor.userId?.startsWith('agent-')) return true;\n\tif (cursor.id?.startsWith('agent-') || cursor.id?.startsWith('agent_')) return true;\n\tif (cursor.username?.startsWith('@agent_') || cursor.username?.startsWith('agent_')) return true;\n\n\tconst roleLower = cursor.role?.toLowerCase() || '';\n\tif (\n\t\troleLower.includes('agent') ||\n\t\troleLower.includes('specialist') ||\n\t\troleLower === 'planner' ||\n\t\troleLower === 'coordinator' ||\n\t\troleLower === 'executor' ||\n\t\troleLower === 'validator' ||\n\t\troleLower === 'supervisor'\n\t) {\n\t\treturn true;\n\t}\n\n\tconst usernameLower = (cursor.username || '').toLowerCase();\n\tif (usernameLower.includes('agent') || usernameLower.includes('specialist')) {\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n","import {\n\tcreateCaptureToolProvider,\n\tunionCaptureRects,\n\ttype CaptureRect,\n\ttype CaptureSurface,\n\ttype CaptureToolProviderOptions,\n\ttype CapturedImage\n} from '../tools/capture.js';\nimport {\n\tCAPTURE_EXCLUDE_SELECTOR,\n\tCAPTURE_OVERLAY_SELECTORS,\n\trasterizeElement,\n\ttype RasterizeElementOptions\n} from '../tools/dom-capture.js';\nimport type { AgentToolProvider } from '../tools/registry.js';\nimport type { CanvasNode, Rect } from './types.js';\n\n/** A flow viewport: screen = flow × zoom + (x, y). */\nexport interface CanvasCaptureViewport {\n\tx: number;\n\ty: number;\n\tzoom: number;\n}\n\nexport interface CanvasCaptureSurfaceOptions<TNode extends CanvasNode = CanvasNode> {\n\t/** The flow's root element (`.react-flow` / `.svelte-flow`), or a getter (re-read on every call). */\n\telement: HTMLElement | null | undefined | (() => HTMLElement | null | undefined);\n\t/** The live viewport (e.g. `useReactFlow().getViewport` / `useSvelteFlow().getViewport`). */\n\tgetViewport: () => CanvasCaptureViewport;\n\t/** The live nodes (e.g. `useReactFlow().getNodes`). */\n\tgetNodes: () => readonly TNode[];\n\t/** Absolute bounds of a node in flow coordinates (default: its position plus its parents', and its measured size). */\n\tgetNodeRect?: (node: TNode, nodes: readonly TNode[]) => Rect | null;\n\t/** Selector of the transformed viewport element inside `element`. */\n\tviewportSelector?: string;\n\t/** Never captured: panels, controls, minimap (default `CANVAS_CAPTURE_CHROME_SELECTORS`). */\n\tchromeSelectors?: readonly string[];\n\t/** Left out unless `includeOverlays` (default `CANVAS_CAPTURE_OVERLAY_SELECTORS`). */\n\toverlaySelectors?: readonly string[];\n\t/** Fill behind the canvas (default: the root element's computed background). */\n\tbackground?: string;\n\t/** Largest output pixels per canvas unit (default 2). */\n\tmaxScale?: number;\n\t/** Swaps the drawing step (tests, or a library such as html-to-image). */\n\trasterize?: (element: HTMLElement, options: RasterizeElementOptions) => Promise<CapturedImage>;\n}\n\n/** The transformed viewport inside React Flow and Svelte Flow. */\nexport const CANVAS_CAPTURE_VIEWPORT_SELECTOR = '.xyflow__viewport, .react-flow__viewport, .svelte-flow__viewport';\n\n/** UI chrome that is never part of a canvas capture. */\nexport const CANVAS_CAPTURE_CHROME_SELECTORS: readonly string[] = [\n\tCAPTURE_EXCLUDE_SELECTOR,\n\t'.react-flow__panel',\n\t'.svelte-flow__panel',\n\t'.react-flow__minimap',\n\t'.svelte-flow__minimap',\n\t'.react-flow__controls',\n\t'.svelte-flow__controls',\n\t'.react-flow__attribution',\n\t'.svelte-flow__attribution'\n];\n\n/** Transient overlays: presence cursors, selection boxes and resize handles. */\nexport const CANVAS_CAPTURE_OVERLAY_SELECTORS: readonly string[] = [\n\t...CAPTURE_OVERLAY_SELECTORS,\n\t'.react-flow__nodesselection',\n\t'.svelte-flow__nodesselection',\n\t'.react-flow__selection',\n\t'.svelte-flow__selection',\n\t'.react-flow__resize-control',\n\t'.svelte-flow__resize-control'\n];\n\nconst size = (node: CanvasNode) => ({\n\twidth: node.measured?.width ?? node.width ?? 0,\n\theight: node.measured?.height ?? node.height ?? 0\n});\n\n/** A node's bounds in flow coordinates: its position plus every parent's, and its measured size. */\nexport function canvasNodeCaptureRect<TNode extends CanvasNode>(node: TNode, nodes: readonly TNode[]): Rect | null {\n\tif (node.hidden) return null;\n\tlet x = node.position.x;\n\tlet y = node.position.y;\n\tconst seen = new Set([node.id]);\n\tlet parentId = node.parentId ?? (node as { parentNode?: string }).parentNode;\n\twhile (parentId && !seen.has(parentId)) {\n\t\tseen.add(parentId);\n\t\tconst parent = nodes.find((n) => n.id === parentId);\n\t\tif (!parent) break;\n\t\tx += parent.position.x;\n\t\ty += parent.position.y;\n\t\tparentId = parent.parentId ?? (parent as { parentNode?: string }).parentNode;\n\t}\n\tconst { width, height } = size(node);\n\treturn { x, y, width, height };\n}\n\n/**\n * A capture surface for a React Flow / Svelte Flow canvas. Units are flow\n * coordinates (node positions). `viewport` draws the canvas as it is on\n * screen (with its background); other regions re-frame the nodes and edges\n * onto a plain background, wherever they are scrolled to. Panels, controls\n * and the minimap are never drawn; selection boxes and presence cursors only\n * with `includeOverlays`.\n */\nexport function createCanvasCaptureSurface<TNode extends CanvasNode = CanvasNode>(\n\toptions: CanvasCaptureSurfaceOptions<TNode>\n): CaptureSurface {\n\tconst getElement = () => (typeof options.element === 'function' ? options.element() : options.element) ?? null;\n\tconst nodeRect = (node: TNode, nodes: readonly TNode[]) => (options.getNodeRect ?? canvasNodeCaptureRect)(node, nodes);\n\tconst rectsOf = (nodes: readonly TNode[], pick: (n: TNode) => boolean) =>\n\t\tnodes.filter(pick).map((n) => nodeRect(n, nodes)).filter((r): r is Rect => !!r && r.width > 0 && r.height > 0);\n\tconst viewportSelector = options.viewportSelector ?? CANVAS_CAPTURE_VIEWPORT_SELECTOR;\n\n\tconst viewport = (): CaptureRect | null => {\n\t\tconst el = getElement();\n\t\tconst vp = options.getViewport();\n\t\tif (!el || !el.clientWidth || !el.clientHeight || !(vp.zoom > 0)) return null;\n\t\treturn { x: -vp.x / vp.zoom, y: -vp.y / vp.zoom, width: el.clientWidth / vp.zoom, height: el.clientHeight / vp.zoom };\n\t};\n\n\treturn {\n\t\tunits: 'canvas units',\n\t\tviewport,\n\t\tcontent: () => unionCaptureRects(rectsOf(options.getNodes(), () => true)),\n\t\tselection: () => {\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst selected = (n: TNode) => !!n.selected && !n.hidden;\n\t\t\treturn { rect: unionCaptureRects(rectsOf(nodes, selected)), ids: nodes.filter(selected).map((n) => n.id) };\n\t\t},\n\t\titems: {\n\t\t\tregion: 'nodes',\n\t\t\tnoun: 'node',\n\t\t\tsource: 'the canvas state',\n\t\t\tbounds: (ids) => {\n\t\t\t\tconst nodes = options.getNodes();\n\t\t\t\tconst missing = ids.filter((id) => !nodes.some((n) => n.id === id));\n\t\t\t\treturn { rect: unionCaptureRects(rectsOf(nodes, (n) => ids.includes(n.id))), missing };\n\t\t\t}\n\t\t},\n\t\toverlays: 'selection boxes, resize handles and agent cursors',\n\t\tmaxScale: options.maxScale ?? 2,\n\t\trender: (request) => {\n\t\t\tconst el = getElement();\n\t\t\tif (!el || !el.isConnected) throw new Error('The canvas is not mounted, so there is nothing to capture.');\n\t\t\tconst rasterize = options.rasterize ?? rasterizeElement;\n\t\t\tconst exclude = [\n\t\t\t\t...(options.chromeSelectors ?? CANVAS_CAPTURE_CHROME_SELECTORS),\n\t\t\t\t...(request.includeOverlays ? [] : (options.overlaySelectors ?? CANVAS_CAPTURE_OVERLAY_SELECTORS))\n\t\t\t];\n\t\t\tconst common = { pixelWidth: request.width, pixelHeight: request.height, format: request.format, quality: request.quality, background: options.background };\n\t\t\tif (request.region === 'viewport') {\n\t\t\t\treturn rasterize(el, { ...common, width: el.clientWidth, height: el.clientHeight, exclude });\n\t\t\t}\n\t\t\t// Re-frame: draw only the viewport's subtree with a transform that puts the rect at the top left.\n\t\t\tconst zoom = request.scale;\n\t\t\treturn rasterize(el, {\n\t\t\t\t...common,\n\t\t\t\twidth: request.width,\n\t\t\t\theight: request.height,\n\t\t\t\texclude,\n\t\t\t\tprepare: (clone) => {\n\t\t\t\t\tconst vpEl = clone.querySelector<HTMLElement>(viewportSelector);\n\t\t\t\t\tif (!vpEl) throw new Error(`The canvas has no viewport element (${viewportSelector}); pass viewportSelector.`);\n\t\t\t\t\tvpEl.style.transform = `translate(${-request.rect.x * zoom}px, ${-request.rect.y * zoom}px) scale(${zoom})`;\n\t\t\t\t\tvpEl.style.transformOrigin = '0 0';\n\t\t\t\t\t// Background patterns and screen-space layers follow the live viewport, not this frame.\n\t\t\t\t\tfor (let node: HTMLElement | null = vpEl; node && node !== clone; node = node.parentElement) {\n\t\t\t\t\t\tfor (const sibling of Array.from(node.parentElement?.children ?? [])) {\n\t\t\t\t\t\t\tif (sibling !== node) sibling.remove();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n}\n\nexport interface CanvasCaptureToolProviderOptions<TNode extends CanvasNode = CanvasNode>\n\textends CanvasCaptureSurfaceOptions<TNode>,\n\t\tCaptureToolProviderOptions {}\n\n/**\n * `capture_view` for a React Flow / Svelte Flow canvas: a screenshot of the\n * viewport, all content, the selection, some nodes by id, or a rect in\n * canvas units. Default id `canvas-capture`.\n */\nexport function createCanvasCaptureToolProvider<TNode extends CanvasNode = CanvasNode>(\n\toptions: CanvasCaptureToolProviderOptions<TNode>\n): AgentToolProvider {\n\treturn createCaptureToolProvider(createCanvasCaptureSurface(options), {\n\t\tsubject: 'the canvas',\n\t\t...options,\n\t\tid: options.id ?? 'canvas-capture'\n\t});\n}\n","/**\n * Provider effects on the canvas (see `@pixerate/editor/effects`): effect fills and overlays on\n * shape nodes (`data.shape.effect`), and transition effects into Scenes (`CanvasScene.transition`).\n *\n * The canvas has no single controller, so `createCanvasEffectsController` works like the other\n * canvas controllers: it reads and writes nodes through `getNodes` / `setNodes`, and records each\n * change as one undoable command on an optional `history`. The framework bindings draw the\n * effects (an animated canvas inside the node) and play scene transitions.\n */\n\nimport { EffectsSlot, resolveEffectValues } from '../effects/helpers.js';\nimport { EffectUnsupportedError } from '../effects/errors.js';\nimport type { EffectDescription, EffectProvider, EffectUse, EffectValue } from '../effects/types.js';\nimport type { Command } from '../history/types.js';\nimport type { CanvasSceneTransition, CanvasScenesStore } from './scenes.js';\nimport { isShapeDescription, type CanvasShapeEffect, type ShapeDescription } from './shapeGeometry.js';\nimport type { CanvasNode } from './types.js';\n\n/** The uses each place takes. */\nexport const CANVAS_EFFECT_PLACES = {\n\t/** Paints a shape's inside. */\n\tfill: ['generator', 'interactive'],\n\t/** Draws over a node's box. */\n\toverlay: ['overlay', 'title', 'interactive', 'generator'],\n\ttransition: ['transition']\n} as const satisfies Record<string, readonly EffectUse[]>;\n\nexport type CanvasEffectPlace = keyof typeof CANVAS_EFFECT_PLACES;\n\n/** The shape effect of a node, if it has one. */\nexport function canvasNodeEffect(node: CanvasNode): CanvasShapeEffect | undefined {\n\tconst shape = (node.data as { shape?: unknown } | undefined)?.shape;\n\treturn isShapeDescription(shape) ? (shape as ShapeDescription).effect : undefined;\n}\n\nexport interface CanvasEffectsControllerOptions<TNode extends CanvasNode = CanvasNode> {\n\tgetNodes: () => TNode[];\n\tsetNodes: (nodes: TNode[]) => void;\n\t/** When set, every change is one undoable command. */\n\thistory?: { execute(command: Command): void };\n\t/** The Scenes store, for transitions between scenes. */\n\tscenes?: CanvasScenesStore;\n\t/** An effects provider (optional; set or change it later with `setEffectsProvider`). */\n\teffects?: EffectProvider | null;\n}\n\n/** Headless effects for a canvas (see the module comment). */\nexport function createCanvasEffectsController<TNode extends CanvasNode = CanvasNode>(options: CanvasEffectsControllerOptions<TNode>) {\n\tconst slot = new EffectsSlot();\n\tif (options.effects) slot.set(options.effects);\n\n\tconst describe = async (effect: string, place: CanvasEffectPlace): Promise<EffectDescription> => {\n\t\tconst d = await slot.require().describe(effect);\n\t\tconst allowed: readonly EffectUse[] = CANVAS_EFFECT_PLACES[place];\n\t\tif (d.kind === 'motion' || !d.uses.some((u) => allowed.includes(u))) {\n\t\t\tconst where = place === 'transition' ? 'a scene transition' : place === 'fill' ? 'a fill' : 'an overlay';\n\t\t\tthrow new EffectUnsupportedError(`\"${d.id}\" (${d.name}) is a ${d.uses.join(' / ')} effect and cannot be ${where}, which takes ${allowed.join(' or ')} effects; search_effects with use \"${allowed[0]}\" lists ones that fit.`, d.id);\n\t\t}\n\t\treturn d;\n\t};\n\n\tconst shapeNode = (id: string): { node: TNode; shape: ShapeDescription } => {\n\t\tconst node = options.getNodes().find((n) => n.id === id);\n\t\tif (!node) throw new Error(`There is no node \"${id}\"; the nodes are ${options.getNodes().map((n) => `\"${n.id}\"`).slice(0, 20).join(', ') || 'none'}.`);\n\t\tconst shape = (node.data as { shape?: unknown } | undefined)?.shape;\n\t\tif (!isShapeDescription(shape)) throw new Error(`Node \"${id}\" is not a shape; effects draw on shape nodes (data.shape).`);\n\t\treturn { node, shape };\n\t};\n\n\tconst commit = (label: string, next: TNode[]) => {\n\t\tconst before = options.getNodes();\n\t\tif (options.history) options.history.execute({ name: label, execute: () => options.setNodes(next), undo: () => options.setNodes(before), redo: () => options.setNodes(next) } as Command);\n\t\telse options.setNodes(next);\n\t};\n\n\tconst withEffect = (id: string, effect: CanvasShapeEffect | undefined): TNode[] =>\n\t\toptions.getNodes().map((n) => {\n\t\t\tif (n.id !== id) return n;\n\t\t\tconst shape = { ...((n.data as { shape: ShapeDescription }).shape) };\n\t\t\tif (effect) shape.effect = effect;\n\t\t\telse delete shape.effect;\n\t\t\treturn { ...n, data: { ...(n.data as object), shape } } as TNode;\n\t\t});\n\n\tconst mergeValues = (d: EffectDescription, was: Record<string, unknown>, values: Record<string, EffectValue | null>) => {\n\t\tconst base = { ...(was as Record<string, EffectValue>) };\n\t\tconst given: Record<string, EffectValue> = {};\n\t\tfor (const [k, v] of Object.entries(values)) {\n\t\t\tif (v !== null) given[k] = v;\n\t\t\telse if (k in d.controls.properties) base[k] = d.defaults[k];\n\t\t\telse throw new Error(`\"${k}\" is not a control of \"${d.id}\"; its controls are ${Object.keys(d.controls.properties).join(', ')}.`);\n\t\t}\n\t\treturn { ...base, ...resolveEffectValues({ ...d, defaults: base }, given) };\n\t};\n\n\treturn {\n\t\t/** The provider slot. */\n\t\teffects: slot,\n\t\tget effectsProvider(): EffectProvider | null {\n\t\t\treturn slot.provider;\n\t\t},\n\t\t/** Registers (or with `null` removes) the effects provider. */\n\t\tsetEffectsProvider(provider: EffectProvider | null) {\n\t\t\tslot.set(provider);\n\t\t},\n\t\t/** Registers `provider` and returns a function that removes it again. */\n\t\tuseEffects(provider: EffectProvider) {\n\t\t\treturn slot.use(provider);\n\t\t},\n\t\t/** The shape nodes that carry effects (or one node's). */\n\t\tlist(nodeId?: string): { nodeId: string; effect: CanvasShapeEffect }[] {\n\t\t\treturn options\n\t\t\t\t.getNodes()\n\t\t\t\t.filter((n) => !nodeId || n.id === nodeId)\n\t\t\t\t.map((n) => ({ nodeId: n.id, effect: canvasNodeEffect(n) }))\n\t\t\t\t.filter((e): e is { nodeId: string; effect: CanvasShapeEffect } => !!e.effect);\n\t\t},\n\t\t/**\n\t\t * Draws a provider effect on a shape node: `fill` paints its inside (generators), `overlay`\n\t\t * draws over its box (overlays, titles, interactive effects). Replaces any effect it had. One\n\t\t * undoable step. Returns the effect.\n\t\t */\n\t\tasync applyNodeEffect(nodeId: string, effect: string, opts: { mode?: 'fill' | 'overlay'; values?: Record<string, EffectValue>; opacity?: number } = {}): Promise<CanvasShapeEffect> {\n\t\t\tshapeNode(nodeId);\n\t\t\tconst mode = opts.mode ?? 'fill';\n\t\t\tconst d = await describe(effect, mode);\n\t\t\tif (opts.opacity !== undefined && !(opts.opacity >= 0 && opts.opacity <= 1)) throw new Error('opacity must be from 0 to 1.');\n\t\t\tconst next: CanvasShapeEffect = { effect: d.id, provider: slot.require().id, name: d.name, values: resolveEffectValues(d, opts.values) as CanvasShapeEffect['values'], mode, ...(opts.opacity !== undefined && opts.opacity !== 1 ? { opacity: opts.opacity } : {}) };\n\t\t\tcommit(`Add effect ${d.name}`, withEffect(nodeId, next));\n\t\t\treturn next;\n\t\t},\n\t\t/** Changes a node's effect: `values` merged (`null` resets), `mode`, `opacity`, `playing`. One undoable step. */\n\t\tasync updateNodeEffect(nodeId: string, changes: { values?: Record<string, EffectValue | null>; mode?: 'fill' | 'overlay'; opacity?: number; playing?: boolean }): Promise<CanvasShapeEffect> {\n\t\t\tconst { shape } = shapeNode(nodeId);\n\t\t\tconst was = shape.effect;\n\t\t\tif (!was) throw new Error(`Node \"${nodeId}\" has no effect; add one with apply_effect.`);\n\t\t\tconst next: CanvasShapeEffect = { ...was };\n\t\t\tif (changes.values || changes.mode) {\n\t\t\t\tconst d = await describe(was.effect, changes.mode ?? was.mode);\n\t\t\t\tif (changes.values) next.values = mergeValues(d, was.values, changes.values) as CanvasShapeEffect['values'];\n\t\t\t\tif (changes.mode) next.mode = changes.mode;\n\t\t\t} else slot.require();\n\t\t\tif (changes.opacity !== undefined) {\n\t\t\t\tif (!(changes.opacity >= 0 && changes.opacity <= 1)) throw new Error('opacity must be from 0 to 1.');\n\t\t\t\tif (changes.opacity === 1) delete next.opacity;\n\t\t\t\telse next.opacity = changes.opacity;\n\t\t\t}\n\t\t\tif (changes.playing !== undefined) {\n\t\t\t\tif (changes.playing) delete next.playing;\n\t\t\t\telse next.playing = false;\n\t\t\t}\n\t\t\tcommit(`Change effect ${was.name ?? was.effect}`, withEffect(nodeId, next));\n\t\t\treturn next;\n\t\t},\n\t\t/** Removes a node's effect (works without a provider). One undoable step. */\n\t\tremoveNodeEffect(nodeId: string): CanvasShapeEffect {\n\t\t\tconst { shape } = shapeNode(nodeId);\n\t\t\tif (!shape.effect) throw new Error(`Node \"${nodeId}\" has no effect.`);\n\t\t\tcommit(`Remove effect ${shape.effect.name ?? shape.effect.effect}`, withEffect(nodeId, undefined));\n\t\t\treturn shape.effect;\n\t\t},\n\t\t/** The scenes, in order (empty without a Scenes store). */\n\t\tscenes() {\n\t\t\treturn options.scenes?.getState().scenes ?? [];\n\t\t},\n\t\t/** The transition into a scene, if it has one. */\n\t\tsceneTransition(sceneId: string): CanvasSceneTransition | undefined {\n\t\t\treturn options.scenes?.getScene(sceneId)?.transition;\n\t\t},\n\t\t/**\n\t\t * Sets (or with `effect` null removes) the provider transition played when the canvas moves to\n\t\t * a scene. Returns the transition.\n\t\t */\n\t\tasync setSceneTransition(sceneId: string, effect: string | null, opts: { values?: Record<string, EffectValue>; duration?: number } = {}): Promise<CanvasSceneTransition | null> {\n\t\t\tconst scenes = options.scenes;\n\t\t\tif (!scenes) throw new Error('This canvas has no Scenes store.');\n\t\t\tif (!scenes.getScene(sceneId)) throw new Error(`There is no scene \"${sceneId}\"; the scenes are ${scenes.getState().scenes.map((s) => `\"${s.id}\"`).join(', ') || 'none'}.`);\n\t\t\tif (effect === null) {\n\t\t\t\tscenes.update(sceneId, { transition: undefined });\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst d = await describe(effect, 'transition');\n\t\t\tconst duration = opts.duration ?? d.duration ?? 600;\n\t\t\tif (!(duration > 0)) throw new Error('duration must be a number of ms above 0.');\n\t\t\tconst transition: CanvasSceneTransition = { effect: d.id, provider: slot.require().id, name: d.name, values: resolveEffectValues(d, opts.values) as CanvasSceneTransition['values'], duration };\n\t\t\tscenes.update(sceneId, { transition });\n\t\t\treturn transition;\n\t\t}\n\t};\n}\n\nexport type CanvasEffectsController<TNode extends CanvasNode = CanvasNode> = ReturnType<typeof createCanvasEffectsController<TNode>>;\n","/**\n * Plays provider effects on a canvas element, for the framework bindings: a node's effect drawn\n * frame by frame (`runCanvasNodeEffect`), and a scene transition between two pictures\n * (`playCanvasSceneTransition`). Renders go through an `EffectRendererPool`, so a page full of\n * effects stays within the browser's WebGL context limit.\n */\n\nimport { EffectRendererPool } from '../effects/pool.js';\nimport type { EffectPixels, EffectProvider } from '../effects/types.js';\nimport type { CanvasSceneTransition } from './scenes.js';\nimport type { CanvasShapeEffect } from './shapeGeometry.js';\nimport { CAPTURE_OVERLAY_SELECTORS, rasterizeElement } from '../tools/dom-capture.js';\n\n/** Where frames are drawn: a `<canvas>` (or anything with a 2D context of that shape). */\nexport interface CanvasEffectTarget {\n\twidth: number;\n\theight: number;\n\tgetContext(id: '2d'): { putImageData(image: ImageData, x: number, y: number): void } | null;\n}\n\nexport interface CanvasEffectClock {\n\tnow(): number;\n\tframe(callback: () => void): () => void;\n}\n\nconst defaultClock: CanvasEffectClock = {\n\tnow: () => (typeof performance !== 'undefined' ? performance.now() : Date.now()),\n\tframe: (cb) => {\n\t\tif (typeof requestAnimationFrame === 'undefined') {\n\t\t\tconst t = setTimeout(cb, 16);\n\t\t\treturn () => clearTimeout(t);\n\t\t}\n\t\tconst id = requestAnimationFrame(cb);\n\t\treturn () => cancelAnimationFrame(id);\n\t}\n};\n\n/** One pool for the canvas effects of a page. */\nexport const canvasEffectsPool = new EffectRendererPool({ maxRenderers: 6 });\n\nfunction draw(target: CanvasEffectTarget, p: EffectPixels) {\n\tconst ctx = target.getContext('2d');\n\tif (!ctx || typeof ImageData === 'undefined') return;\n\tctx.putImageData(new ImageData(new Uint8ClampedArray(p.data), p.width, p.height), 0, 0);\n}\n\n/**\n * Draws a node's effect into `target` (sized to the node in device pixels): one frame for a still\n * effect or when `playing` is false, else a frame per animation frame (one render in flight at a\n * time) until `stop`. `onError` hears a failed render, after which drawing stops.\n */\nexport function runCanvasNodeEffect(options: { provider: EffectProvider; effect: CanvasShapeEffect; target: CanvasEffectTarget; animate?: boolean; clock?: CanvasEffectClock; pool?: EffectRendererPool; onError?: (error: unknown) => void }): { stop(): void } {\n\tconst { provider, effect, target } = options;\n\tconst clock = options.clock ?? defaultClock;\n\tconst pool = options.pool ?? canvasEffectsPool;\n\tconst size = { width: Math.max(1, Math.round(target.width)), height: Math.max(1, Math.round(target.height)) };\n\tconst start = clock.now();\n\tlet stopped = false;\n\tlet cancel = () => {};\n\tconst tick = () => {\n\t\tif (stopped) return;\n\t\tconst time = options.animate === false || effect.playing === false ? 0 : clock.now() - start;\n\t\tpool.render(provider, effect.effect, size, { time, values: effect.values }).then(\n\t\t\t(p) => {\n\t\t\t\tif (stopped) return;\n\t\t\t\tdraw(target, p);\n\t\t\t\tif (options.animate !== false && effect.playing !== false) cancel = clock.frame(tick);\n\t\t\t},\n\t\t\t(e: unknown) => {\n\t\t\t\tif (!stopped) options.onError?.(e);\n\t\t\t}\n\t\t);\n\t};\n\ttick();\n\treturn {\n\t\tstop() {\n\t\t\tstopped = true;\n\t\t\tcancel();\n\t\t}\n\t};\n}\n\n/** Plays a scene transition from `from` to `to` (same size as `target`), resolving when it ends. */\nexport async function playCanvasSceneTransition(options: { provider: EffectProvider; transition: CanvasSceneTransition; from: EffectPixels; to: EffectPixels; target: CanvasEffectTarget; clock?: CanvasEffectClock; pool?: EffectRendererPool }): Promise<void> {\n\tconst { provider, transition, from, to, target } = options;\n\tconst clock = options.clock ?? defaultClock;\n\tconst pool = options.pool ?? canvasEffectsPool;\n\tconst duration = transition.duration ?? 600;\n\tconst size = { width: from.width, height: from.height };\n\tconst start = clock.now();\n\tfor (;;) {\n\t\tconst time = Math.min(duration, clock.now() - start);\n\t\tconst p = await pool.render(provider, transition.effect, size, { time, progress: time / duration, values: transition.values, inputs: { from, to } });\n\t\tdraw(target, p);\n\t\tif (time >= duration) return;\n\t\tawait new Promise<void>((resolve) => clock.frame(resolve));\n\t}\n}\n\n/**\n * The pixels of a DOM element as it looks now (the canvas view, for a scene transition), at\n * `width × height` device pixels. Browser only; overlays marked `data-capture=\"overlay\"` are left out.\n */\nexport async function captureCanvasView(element: HTMLElement, size: { width: number; height: number }): Promise<EffectPixels> {\n\tconst url = await rasterizeElement(element, { pixelWidth: size.width, pixelHeight: size.height, exclude: CAPTURE_OVERLAY_SELECTORS });\n\tconst doc = element.ownerDocument;\n\tconst image = new (doc.defaultView as Window & typeof globalThis).Image();\n\tawait new Promise<void>((resolve, reject) => {\n\t\timage.onload = () => resolve();\n\t\timage.onerror = () => reject(new Error('The canvas view could not be captured.'));\n\t\timage.src = url;\n\t});\n\tconst canvas = doc.createElement('canvas');\n\tcanvas.width = size.width;\n\tcanvas.height = size.height;\n\tconst ctx = canvas.getContext('2d');\n\tif (!ctx) throw new Error('The canvas view could not be captured (no 2D canvas).');\n\tctx.drawImage(image, 0, 0, size.width, size.height);\n\tconst data = ctx.getImageData(0, 0, size.width, size.height).data;\n\treturn { width: size.width, height: size.height, data };\n}\n\n/**\n * Moves a canvas view to a scene through its transition: captures the view, `jump`s there (no\n * animated move), waits for the view to draw, captures it again and plays the transition on an\n * overlay canvas laid over `element` (removed afterwards). Falls back to just jumping when\n * anything is missing or fails.\n */\nexport async function transitionCanvasView(options: { provider: EffectProvider; transition: CanvasSceneTransition; element: HTMLElement; jump: () => void; scale?: number; clock?: CanvasEffectClock }): Promise<void> {\n\tconst { element } = options;\n\tconst clock = options.clock ?? defaultClock;\n\tconst scale = options.scale ?? Math.min(1, typeof devicePixelRatio === 'number' ? devicePixelRatio : 1);\n\tconst size = { width: Math.max(1, Math.round(element.clientWidth * scale)), height: Math.max(1, Math.round(element.clientHeight * scale)) };\n\tlet from: EffectPixels;\n\ttry {\n\t\tfrom = await captureCanvasView(element, size);\n\t} catch {\n\t\toptions.jump();\n\t\treturn;\n\t}\n\tconst overlay = element.ownerDocument.createElement('canvas');\n\toverlay.width = size.width;\n\toverlay.height = size.height;\n\toverlay.setAttribute('data-capture', 'overlay');\n\toverlay.setAttribute('data-canvas-transition', options.transition.effect);\n\tObject.assign(overlay.style, { position: 'absolute', inset: '0', width: '100%', height: '100%', pointerEvents: 'none', zIndex: '1000' });\n\toverlay.getContext('2d')?.putImageData(new ImageData(new Uint8ClampedArray(from.data), size.width, size.height), 0, 0);\n\tif (getComputedStyle(element).position === 'static') element.style.position = 'relative';\n\telement.appendChild(overlay);\n\ttry {\n\t\toptions.jump();\n\t\tawait new Promise<void>((r) => clock.frame(() => clock.frame(r)));\n\t\tconst to = await captureCanvasView(element, size);\n\t\tawait playCanvasSceneTransition({ provider: options.provider, transition: options.transition, from, to, target: overlay, clock });\n\t} catch {\n\t\t// The jump has happened; the transition is only decoration.\n\t} finally {\n\t\toverlay.remove();\n\t}\n}\n","/**\n * Agent tools for provider effects on the canvas: the shared effect tools on shape nodes\n * (`apply_effect` fills a shape with a generator or lays an overlay over it), plus\n * `set_canvas_effect_mode`, `list_scene_transitions` and `set_scene_transition`.\n */\n\nimport { createEffectToolProvider, type AppliedEffectInfo, type EffectChangeResult } from '../effects/tools.js';\nimport type { EffectValue } from '../effects/types.js';\nimport type { AgentTool, AgentToolProvider } from '../tools/registry.js';\nimport type { CanvasEffectsController } from './effects.js';\nimport type { CanvasShapeEffect } from './shapeGeometry.js';\n\nexport interface CanvasEffectsToolProviderOptions {\n\t/** Provider id (unique per registry). Default `canvas-effects`. */\n\tid?: string;\n\t/** Always prefix tool names with `<namespace>__`. */\n\tnamespace?: string;\n\t/** The canvas cannot be edited: write tools are hidden. A string is the reason. */\n\treadOnly?: boolean | string | (() => boolean | string);\n\t/** Lets the tool list follow changes (e.g. the host's node store subscribe). */\n\tsubscribe?: (notify: () => void) => () => void;\n}\n\nconst info = (e: CanvasShapeEffect): AppliedEffectInfo => ({\n\tid: 'effect',\n\teffect: e.effect,\n\t...(e.name ? { name: e.name } : {}),\n\t...(e.provider ? { provider: e.provider } : {}),\n\trole: e.mode === 'fill' ? 'fill' : 'overlay',\n\tenabled: e.playing !== false,\n\topacity: e.opacity ?? 1,\n\tvalues: { ...e.values }\n});\n\n/** The effect tools for a canvas (see the module comment). */\nexport function createCanvasEffectsToolProvider(controller: CanvasEffectsController, options: CanvasEffectsToolProviderOptions = {}): AgentToolProvider {\n\tconst readOnlyReason = (): string | null => {\n\t\tconst ro = typeof options.readOnly === 'function' ? options.readOnly() : options.readOnly;\n\t\treturn ro ? (typeof ro === 'string' ? ro : 'The canvas is read-only.') : null;\n\t};\n\tconst result = (nodeId: string, effect?: CanvasShapeEffect): EffectChangeResult => {\n\t\tconst now = controller.list(nodeId)[0]?.effect;\n\t\treturn { target: nodeId, effect: info((effect ?? now)!), effects: now ? [info(now)] : [] };\n\t};\n\tconst once = (instance: string) => {\n\t\tif (instance !== 'effect') throw new Error('A shape node has one effect, \"effect\".');\n\t};\n\treturn createEffectToolProvider({\n\t\tid: options.id ?? 'canvas-effects',\n\t\tnamespace: options.namespace,\n\t\ttarget: { noun: 'node', id: 'A shape node id on the canvas (a node with data.shape).' },\n\t\tapplyUses: ['generator', 'overlay', 'title', 'interactive'],\n\t\tapplyDetails:\n\t\t\t'Generators fill the shape (clipped to its outline); overlays, titles and interactive effects draw over the node\\'s box (set_canvas_effect_mode switches). A node has one effect: this replaces it. enabled false holds the first frame; opacity mixes it. index does not apply.',\n\t\tprovider: () => controller.effectsProvider,\n\t\treadOnlyReason,\n\t\tlist: (target) => controller.list(target).map((e) => ({ target: e.nodeId, effects: [info(e.effect)] })),\n\t\tapply: async (target, effect, args) => {\n\t\t\tif (args.index !== undefined) throw new Error('index does not apply: a shape node has one effect.');\n\t\t\tconst provider = controller.effectsProvider;\n\t\t\tconst d = provider ? await provider.describe(effect) : null;\n\t\t\tconst mode = d && !d.uses.includes('generator') ? 'overlay' : 'fill';\n\t\t\tconst applied = await controller.applyNodeEffect(target, effect, { mode, values: args.values as Record<string, EffectValue> | undefined, opacity: args.opacity });\n\t\t\tif (args.enabled === false) await controller.updateNodeEffect(target, { playing: false });\n\t\t\treturn result(target, args.enabled === false ? { ...applied, playing: false } : applied);\n\t\t},\n\t\tupdate: async (target, instance, changes) => {\n\t\t\tonce(instance);\n\t\t\tif (changes.index !== undefined) throw new Error('index does not apply: a shape node has one effect.');\n\t\t\tconst e = await controller.updateNodeEffect(target, { values: changes.values as Record<string, EffectValue | null> | undefined, opacity: changes.opacity, playing: changes.enabled });\n\t\t\treturn result(target, e);\n\t\t},\n\t\tremove: async (target, instance) => {\n\t\t\tonce(instance);\n\t\t\tconst was = controller.removeNodeEffect(target);\n\t\t\treturn { target, effect: info(was), effects: [] };\n\t\t},\n\t\tsubscribe: options.subscribe ? (notify) => options.subscribe!(notify) : (notify) => controller.effects.subscribe(() => notify()),\n\t\textraTools: (ctx): AgentTool[] => [\n\t\t\t{\n\t\t\t\tname: 'set_canvas_effect_mode',\n\t\t\t\ttitle: \"Set a node effect's mode\",\n\t\t\t\tdescription: 'Switches a shape node\\'s effect between fill (paints the inside, clipped to the outline) and overlay (draws over the node\\'s box). One undoable step. Returns the effect.',\n\t\t\t\tinputSchema: {\n\t\t\t\t\ttype: 'object',\n\t\t\t\t\tproperties: {\n\t\t\t\t\t\tnode: { type: 'string', minLength: 1, description: 'A shape node id from list_applied_effects.' },\n\t\t\t\t\t\tmode: { type: 'string', enum: ['fill', 'overlay'], description: 'fill or overlay.' }\n\t\t\t\t\t},\n\t\t\t\t\trequired: ['node', 'mode'],\n\t\t\t\t\tadditionalProperties: false\n\t\t\t\t},\n\t\t\t\tannotations: { title: \"Set a node effect's mode\", ...ctx.annotations.writeIdempotent },\n\t\t\t\thandler: async (args) => {\n\t\t\t\t\tctx.requireProvider();\n\t\t\t\t\treturn { node: args.node, effect: info(await controller.updateNodeEffect(args.node, { mode: args.mode })) };\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'list_scene_transitions',\n\t\t\t\ttitle: 'List scene transitions',\n\t\t\t\tdescription: 'Lists the canvas Scenes in order with the transition effect played when moving to each (or null). Works without a provider.',\n\t\t\t\tinputSchema: { type: 'object', properties: {}, additionalProperties: false },\n\t\t\t\tannotations: { title: 'List scene transitions', ...ctx.annotations.read },\n\t\t\t\thandler: () => ({ scenes: controller.scenes().map((s) => ({ id: s.id, name: s.name, transition: s.transition ?? null })) })\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'set_scene_transition',\n\t\t\t\ttitle: 'Set a scene transition',\n\t\t\t\tdescription:\n\t\t\t\t\t'Sets the provider transition effect (search_effects with use transition) played when the canvas moves to a scene: the view before and after are captured and the effect blends between them. effect null removes it. Returns the transition.',\n\t\t\t\tinputSchema: {\n\t\t\t\t\ttype: 'object',\n\t\t\t\t\tproperties: {\n\t\t\t\t\t\tscene: { type: 'string', minLength: 1, description: 'A scene id from list_scene_transitions.' },\n\t\t\t\t\t\teffect: { anyOf: [ctx.effectIdSchema(['transition']), { type: 'null' }], description: 'A transition effect id from search_effects, or null to remove the transition.' },\n\t\t\t\t\t\tvalues: ctx.valuesSchema(\"Control values over the effect's defaults\"),\n\t\t\t\t\t\tduration: { type: 'number', exclusiveMinimum: 0, description: \"Length in ms (default: the effect's own, else 600).\" }\n\t\t\t\t\t},\n\t\t\t\t\trequired: ['scene', 'effect'],\n\t\t\t\t\tadditionalProperties: false\n\t\t\t\t},\n\t\t\t\tannotations: { title: 'Set a scene transition', ...ctx.annotations.writeIdempotent },\n\t\t\t\thandler: async (args) => {\n\t\t\t\t\tif (args.effect !== null) ctx.requireProvider();\n\t\t\t\t\treturn { scene: args.scene, transition: await controller.setSceneTransition(args.scene, args.effect, { values: args.values, duration: args.duration }) };\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t});\n}\n"]}
|