How do I have so many partners??
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Initial build

aria 1b77f4d7

+3870
+3
.gitignore
··· 1 + node_modules/ 2 + dist/ 3 + *.tsbuildinfo
+124
README.md
··· 1 + # polymap 2 + 3 + Generate interactive polycule relationship maps from a YAML config file. 4 + 5 + Outputs a self-contained interactive HTML page, an embeddable JS snippet, a static SVG, and a PNG image. 6 + 7 + ## Requirements 8 + 9 + - Node.js 18+ 10 + 11 + ## Setup 12 + 13 + ```bash 14 + npm install 15 + npm run build 16 + ``` 17 + 18 + ## Usage 19 + 20 + ```bash 21 + node dist/cli.js generate <input.yaml> [options] 22 + ``` 23 + 24 + ### Options 25 + 26 + | Flag | Default | Description | 27 + |---|---|---| 28 + | `-o, --output <dir>` | `.` | Output directory | 29 + | `-f, --format <list>` | `html,svg,png` | Comma-separated formats to generate | 30 + | `-n, --name <prefix>` | input filename | Output filename prefix | 31 + | `--width <px>` | `1400` | PNG output width in pixels | 32 + | `--title <title>` | `Polycule Map` | HTML page title | 33 + 34 + ### Examples 35 + 36 + ```bash 37 + # Generate all formats 38 + node dist/cli.js generate polycule.yaml --output ./out 39 + 40 + # HTML only, custom name 41 + node dist/cli.js generate polycule.yaml --output ./out --format html --name my-polycule 42 + 43 + # PNG only, higher resolution 44 + node dist/cli.js generate polycule.yaml --output ./out --format png --width 2400 45 + ``` 46 + 47 + ## Config file format 48 + 49 + ```yaml 50 + settings: 51 + theme: dark # dark | light 52 + nodeScale: uniform # uniform | connections (scales node size by number of relationships) 53 + 54 + people: 55 + - id: alice # unique identifier used in relationships 56 + name: Alice # display name 57 + pronouns: she/her # optional 58 + photo: "https://example.com/alice.jpg" # optional, URL to profile picture 59 + color: "#e91e8c" # optional, overrides auto-assigned colour 60 + links: 61 + - label: Instagram 62 + url: "https://instagram.com/alice" 63 + - label: Website 64 + url: "https://alice.me" 65 + 66 + relationships: 67 + - from: alice 68 + to: bob 69 + type: partner 70 + label: "since 2021" # optional, overrides the default type label on the edge 71 + ``` 72 + 73 + ### Relationship types 74 + 75 + | Type | Line style | 76 + |---|---| 77 + | `partner` | Solid, pink | 78 + | `nesting_partner` | Double line, red | 79 + | `anchor_partner` | Dashed thick, dark red | 80 + | `fwb` | Dashed, purple | 81 + | `casual` | Dotted, cyan | 82 + | `queerplatonic` | Dash-dot, teal | 83 + | `comet` | Long dash, grey | 84 + | `friend` | Solid thin, light blue | 85 + | `metamour` | Faint dotted, dark grey | 86 + 87 + ## Embedding in a website 88 + 89 + Two files are generated for the `html` format: 90 + 91 + **Standalone page** (`polycule.html`) — open directly in a browser or host as-is. Can also be embedded via `<iframe>`. 92 + 93 + **Embed script** (`polycule-embed.js`) — drop into any existing page: 94 + 95 + ```html 96 + <div id="polymap-root" style="width: 100%; height: 600px;"></div> 97 + <script src="polycule-embed.js"></script> 98 + ``` 99 + 100 + No framework required. Works in any HTML page. 101 + 102 + ## Interactive controls 103 + 104 + - **Drag** a node to reposition it (pins in place) 105 + - **Double-click** a pinned node to release it back into the simulation 106 + - **Scroll / pinch** to zoom 107 + - **Click and drag** the background to pan 108 + - **Click** a node to open an info panel with name, pronouns, and links 109 + - **☀ Light / ☾ Dark** button — toggle theme 110 + - **Legend** button — show/hide relationship type key 111 + - **⊡ Fit** button — fit the whole graph into view 112 + 113 + ## Development 114 + 115 + ```bash 116 + # Run CLI directly without building (requires tsx) 117 + npx tsx src/cli.ts generate example.yaml --output ./out 118 + 119 + # Rebuild client bundle only (after editing src/client/graph.ts) 120 + npm run build:client 121 + 122 + # Rebuild CLI only (after editing anything except src/client/) 123 + npm run build:cli 124 + ```
+85
example.yaml
··· 1 + settings: 2 + theme: dark 3 + nodeScale: uniform 4 + 5 + people: 6 + - id: aria 7 + name: Aria 8 + pronouns: she/her 9 + color: "#e91e8c" 10 + links: 11 + - label: Website 12 + url: "https://example.com/aria" 13 + - label: Instagram 14 + url: "https://instagram.com/aria" 15 + 16 + - id: blake 17 + name: Blake 18 + pronouns: they/them 19 + color: "#9c27b0" 20 + links: 21 + - label: Mastodon 22 + url: "https://mastodon.social/@blake" 23 + 24 + - id: cassidy 25 + name: Cassidy 26 + pronouns: she/they 27 + color: "#2196f3" 28 + 29 + - id: drew 30 + name: Drew 31 + pronouns: he/him 32 + color: "#009688" 33 + links: 34 + - label: Twitter 35 + url: "https://twitter.com/drew" 36 + 37 + - id: elliot 38 + name: Elliot 39 + pronouns: he/they 40 + color: "#ff9800" 41 + 42 + - id: frankie 43 + name: Frankie 44 + pronouns: any/all 45 + color: "#4caf50" 46 + 47 + relationships: 48 + - from: aria 49 + to: blake 50 + type: partner 51 + label: "partners" 52 + 53 + - from: aria 54 + to: cassidy 55 + type: nesting_partner 56 + label: "nesting" 57 + 58 + - from: blake 59 + to: drew 60 + type: queerplatonic 61 + 62 + - from: cassidy 63 + to: drew 64 + type: fwb 65 + 66 + - from: blake 67 + to: elliot 68 + type: casual 69 + 70 + - from: drew 71 + to: frankie 72 + type: anchor_partner 73 + label: "anchors" 74 + 75 + - from: aria 76 + to: elliot 77 + type: friend 78 + 79 + - from: cassidy 80 + to: frankie 81 + type: comet 82 + 83 + - from: elliot 84 + to: frankie 85 + type: metamour
+2067
package-lock.json
··· 1 + { 2 + "name": "polymap", 3 + "version": "0.1.0", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "polymap", 9 + "version": "0.1.0", 10 + "dependencies": { 11 + "@resvg/resvg-js": "^2.6.0", 12 + "commander": "^12.0.0", 13 + "d3-drag": "^3.0.0", 14 + "d3-force": "^3.0.0", 15 + "d3-selection": "^3.0.0", 16 + "d3-zoom": "^3.0.0", 17 + "js-yaml": "^4.1.0", 18 + "sharp": "^0.33.0" 19 + }, 20 + "bin": { 21 + "polymap": "dist/cli.js" 22 + }, 23 + "devDependencies": { 24 + "@types/d3-drag": "^3.0.7", 25 + "@types/d3-force": "^3.0.10", 26 + "@types/d3-selection": "^3.0.10", 27 + "@types/d3-zoom": "^3.0.8", 28 + "@types/js-yaml": "^4.0.9", 29 + "@types/node": "^20.0.0", 30 + "esbuild": "^0.25.0", 31 + "tsx": "^4.7.0", 32 + "typescript": "^5.4.0" 33 + }, 34 + "engines": { 35 + "node": ">=18.0.0" 36 + } 37 + }, 38 + "node_modules/@emnapi/runtime": { 39 + "version": "1.9.2", 40 + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", 41 + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", 42 + "license": "MIT", 43 + "optional": true, 44 + "dependencies": { 45 + "tslib": "^2.4.0" 46 + } 47 + }, 48 + "node_modules/@esbuild/aix-ppc64": { 49 + "version": "0.25.12", 50 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", 51 + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", 52 + "cpu": [ 53 + "ppc64" 54 + ], 55 + "dev": true, 56 + "license": "MIT", 57 + "optional": true, 58 + "os": [ 59 + "aix" 60 + ], 61 + "engines": { 62 + "node": ">=18" 63 + } 64 + }, 65 + "node_modules/@esbuild/android-arm": { 66 + "version": "0.25.12", 67 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", 68 + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", 69 + "cpu": [ 70 + "arm" 71 + ], 72 + "dev": true, 73 + "license": "MIT", 74 + "optional": true, 75 + "os": [ 76 + "android" 77 + ], 78 + "engines": { 79 + "node": ">=18" 80 + } 81 + }, 82 + "node_modules/@esbuild/android-arm64": { 83 + "version": "0.25.12", 84 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", 85 + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", 86 + "cpu": [ 87 + "arm64" 88 + ], 89 + "dev": true, 90 + "license": "MIT", 91 + "optional": true, 92 + "os": [ 93 + "android" 94 + ], 95 + "engines": { 96 + "node": ">=18" 97 + } 98 + }, 99 + "node_modules/@esbuild/android-x64": { 100 + "version": "0.25.12", 101 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", 102 + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", 103 + "cpu": [ 104 + "x64" 105 + ], 106 + "dev": true, 107 + "license": "MIT", 108 + "optional": true, 109 + "os": [ 110 + "android" 111 + ], 112 + "engines": { 113 + "node": ">=18" 114 + } 115 + }, 116 + "node_modules/@esbuild/darwin-arm64": { 117 + "version": "0.25.12", 118 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", 119 + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", 120 + "cpu": [ 121 + "arm64" 122 + ], 123 + "dev": true, 124 + "license": "MIT", 125 + "optional": true, 126 + "os": [ 127 + "darwin" 128 + ], 129 + "engines": { 130 + "node": ">=18" 131 + } 132 + }, 133 + "node_modules/@esbuild/darwin-x64": { 134 + "version": "0.25.12", 135 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", 136 + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", 137 + "cpu": [ 138 + "x64" 139 + ], 140 + "dev": true, 141 + "license": "MIT", 142 + "optional": true, 143 + "os": [ 144 + "darwin" 145 + ], 146 + "engines": { 147 + "node": ">=18" 148 + } 149 + }, 150 + "node_modules/@esbuild/freebsd-arm64": { 151 + "version": "0.25.12", 152 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", 153 + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", 154 + "cpu": [ 155 + "arm64" 156 + ], 157 + "dev": true, 158 + "license": "MIT", 159 + "optional": true, 160 + "os": [ 161 + "freebsd" 162 + ], 163 + "engines": { 164 + "node": ">=18" 165 + } 166 + }, 167 + "node_modules/@esbuild/freebsd-x64": { 168 + "version": "0.25.12", 169 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", 170 + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", 171 + "cpu": [ 172 + "x64" 173 + ], 174 + "dev": true, 175 + "license": "MIT", 176 + "optional": true, 177 + "os": [ 178 + "freebsd" 179 + ], 180 + "engines": { 181 + "node": ">=18" 182 + } 183 + }, 184 + "node_modules/@esbuild/linux-arm": { 185 + "version": "0.25.12", 186 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", 187 + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", 188 + "cpu": [ 189 + "arm" 190 + ], 191 + "dev": true, 192 + "license": "MIT", 193 + "optional": true, 194 + "os": [ 195 + "linux" 196 + ], 197 + "engines": { 198 + "node": ">=18" 199 + } 200 + }, 201 + "node_modules/@esbuild/linux-arm64": { 202 + "version": "0.25.12", 203 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", 204 + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", 205 + "cpu": [ 206 + "arm64" 207 + ], 208 + "dev": true, 209 + "license": "MIT", 210 + "optional": true, 211 + "os": [ 212 + "linux" 213 + ], 214 + "engines": { 215 + "node": ">=18" 216 + } 217 + }, 218 + "node_modules/@esbuild/linux-ia32": { 219 + "version": "0.25.12", 220 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", 221 + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", 222 + "cpu": [ 223 + "ia32" 224 + ], 225 + "dev": true, 226 + "license": "MIT", 227 + "optional": true, 228 + "os": [ 229 + "linux" 230 + ], 231 + "engines": { 232 + "node": ">=18" 233 + } 234 + }, 235 + "node_modules/@esbuild/linux-loong64": { 236 + "version": "0.25.12", 237 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", 238 + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", 239 + "cpu": [ 240 + "loong64" 241 + ], 242 + "dev": true, 243 + "license": "MIT", 244 + "optional": true, 245 + "os": [ 246 + "linux" 247 + ], 248 + "engines": { 249 + "node": ">=18" 250 + } 251 + }, 252 + "node_modules/@esbuild/linux-mips64el": { 253 + "version": "0.25.12", 254 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", 255 + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", 256 + "cpu": [ 257 + "mips64el" 258 + ], 259 + "dev": true, 260 + "license": "MIT", 261 + "optional": true, 262 + "os": [ 263 + "linux" 264 + ], 265 + "engines": { 266 + "node": ">=18" 267 + } 268 + }, 269 + "node_modules/@esbuild/linux-ppc64": { 270 + "version": "0.25.12", 271 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", 272 + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", 273 + "cpu": [ 274 + "ppc64" 275 + ], 276 + "dev": true, 277 + "license": "MIT", 278 + "optional": true, 279 + "os": [ 280 + "linux" 281 + ], 282 + "engines": { 283 + "node": ">=18" 284 + } 285 + }, 286 + "node_modules/@esbuild/linux-riscv64": { 287 + "version": "0.25.12", 288 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", 289 + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", 290 + "cpu": [ 291 + "riscv64" 292 + ], 293 + "dev": true, 294 + "license": "MIT", 295 + "optional": true, 296 + "os": [ 297 + "linux" 298 + ], 299 + "engines": { 300 + "node": ">=18" 301 + } 302 + }, 303 + "node_modules/@esbuild/linux-s390x": { 304 + "version": "0.25.12", 305 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", 306 + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", 307 + "cpu": [ 308 + "s390x" 309 + ], 310 + "dev": true, 311 + "license": "MIT", 312 + "optional": true, 313 + "os": [ 314 + "linux" 315 + ], 316 + "engines": { 317 + "node": ">=18" 318 + } 319 + }, 320 + "node_modules/@esbuild/linux-x64": { 321 + "version": "0.25.12", 322 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", 323 + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", 324 + "cpu": [ 325 + "x64" 326 + ], 327 + "dev": true, 328 + "license": "MIT", 329 + "optional": true, 330 + "os": [ 331 + "linux" 332 + ], 333 + "engines": { 334 + "node": ">=18" 335 + } 336 + }, 337 + "node_modules/@esbuild/netbsd-arm64": { 338 + "version": "0.25.12", 339 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", 340 + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", 341 + "cpu": [ 342 + "arm64" 343 + ], 344 + "dev": true, 345 + "license": "MIT", 346 + "optional": true, 347 + "os": [ 348 + "netbsd" 349 + ], 350 + "engines": { 351 + "node": ">=18" 352 + } 353 + }, 354 + "node_modules/@esbuild/netbsd-x64": { 355 + "version": "0.25.12", 356 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", 357 + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", 358 + "cpu": [ 359 + "x64" 360 + ], 361 + "dev": true, 362 + "license": "MIT", 363 + "optional": true, 364 + "os": [ 365 + "netbsd" 366 + ], 367 + "engines": { 368 + "node": ">=18" 369 + } 370 + }, 371 + "node_modules/@esbuild/openbsd-arm64": { 372 + "version": "0.25.12", 373 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", 374 + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", 375 + "cpu": [ 376 + "arm64" 377 + ], 378 + "dev": true, 379 + "license": "MIT", 380 + "optional": true, 381 + "os": [ 382 + "openbsd" 383 + ], 384 + "engines": { 385 + "node": ">=18" 386 + } 387 + }, 388 + "node_modules/@esbuild/openbsd-x64": { 389 + "version": "0.25.12", 390 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", 391 + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", 392 + "cpu": [ 393 + "x64" 394 + ], 395 + "dev": true, 396 + "license": "MIT", 397 + "optional": true, 398 + "os": [ 399 + "openbsd" 400 + ], 401 + "engines": { 402 + "node": ">=18" 403 + } 404 + }, 405 + "node_modules/@esbuild/openharmony-arm64": { 406 + "version": "0.25.12", 407 + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", 408 + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", 409 + "cpu": [ 410 + "arm64" 411 + ], 412 + "dev": true, 413 + "license": "MIT", 414 + "optional": true, 415 + "os": [ 416 + "openharmony" 417 + ], 418 + "engines": { 419 + "node": ">=18" 420 + } 421 + }, 422 + "node_modules/@esbuild/sunos-x64": { 423 + "version": "0.25.12", 424 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", 425 + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", 426 + "cpu": [ 427 + "x64" 428 + ], 429 + "dev": true, 430 + "license": "MIT", 431 + "optional": true, 432 + "os": [ 433 + "sunos" 434 + ], 435 + "engines": { 436 + "node": ">=18" 437 + } 438 + }, 439 + "node_modules/@esbuild/win32-arm64": { 440 + "version": "0.25.12", 441 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", 442 + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", 443 + "cpu": [ 444 + "arm64" 445 + ], 446 + "dev": true, 447 + "license": "MIT", 448 + "optional": true, 449 + "os": [ 450 + "win32" 451 + ], 452 + "engines": { 453 + "node": ">=18" 454 + } 455 + }, 456 + "node_modules/@esbuild/win32-ia32": { 457 + "version": "0.25.12", 458 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", 459 + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", 460 + "cpu": [ 461 + "ia32" 462 + ], 463 + "dev": true, 464 + "license": "MIT", 465 + "optional": true, 466 + "os": [ 467 + "win32" 468 + ], 469 + "engines": { 470 + "node": ">=18" 471 + } 472 + }, 473 + "node_modules/@esbuild/win32-x64": { 474 + "version": "0.25.12", 475 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", 476 + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", 477 + "cpu": [ 478 + "x64" 479 + ], 480 + "dev": true, 481 + "license": "MIT", 482 + "optional": true, 483 + "os": [ 484 + "win32" 485 + ], 486 + "engines": { 487 + "node": ">=18" 488 + } 489 + }, 490 + "node_modules/@img/sharp-darwin-arm64": { 491 + "version": "0.33.5", 492 + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", 493 + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", 494 + "cpu": [ 495 + "arm64" 496 + ], 497 + "license": "Apache-2.0", 498 + "optional": true, 499 + "os": [ 500 + "darwin" 501 + ], 502 + "engines": { 503 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 504 + }, 505 + "funding": { 506 + "url": "https://opencollective.com/libvips" 507 + }, 508 + "optionalDependencies": { 509 + "@img/sharp-libvips-darwin-arm64": "1.0.4" 510 + } 511 + }, 512 + "node_modules/@img/sharp-darwin-x64": { 513 + "version": "0.33.5", 514 + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", 515 + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", 516 + "cpu": [ 517 + "x64" 518 + ], 519 + "license": "Apache-2.0", 520 + "optional": true, 521 + "os": [ 522 + "darwin" 523 + ], 524 + "engines": { 525 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 526 + }, 527 + "funding": { 528 + "url": "https://opencollective.com/libvips" 529 + }, 530 + "optionalDependencies": { 531 + "@img/sharp-libvips-darwin-x64": "1.0.4" 532 + } 533 + }, 534 + "node_modules/@img/sharp-libvips-darwin-arm64": { 535 + "version": "1.0.4", 536 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", 537 + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", 538 + "cpu": [ 539 + "arm64" 540 + ], 541 + "license": "LGPL-3.0-or-later", 542 + "optional": true, 543 + "os": [ 544 + "darwin" 545 + ], 546 + "funding": { 547 + "url": "https://opencollective.com/libvips" 548 + } 549 + }, 550 + "node_modules/@img/sharp-libvips-darwin-x64": { 551 + "version": "1.0.4", 552 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", 553 + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", 554 + "cpu": [ 555 + "x64" 556 + ], 557 + "license": "LGPL-3.0-or-later", 558 + "optional": true, 559 + "os": [ 560 + "darwin" 561 + ], 562 + "funding": { 563 + "url": "https://opencollective.com/libvips" 564 + } 565 + }, 566 + "node_modules/@img/sharp-libvips-linux-arm": { 567 + "version": "1.0.5", 568 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", 569 + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", 570 + "cpu": [ 571 + "arm" 572 + ], 573 + "libc": [ 574 + "glibc" 575 + ], 576 + "license": "LGPL-3.0-or-later", 577 + "optional": true, 578 + "os": [ 579 + "linux" 580 + ], 581 + "funding": { 582 + "url": "https://opencollective.com/libvips" 583 + } 584 + }, 585 + "node_modules/@img/sharp-libvips-linux-arm64": { 586 + "version": "1.0.4", 587 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", 588 + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", 589 + "cpu": [ 590 + "arm64" 591 + ], 592 + "libc": [ 593 + "glibc" 594 + ], 595 + "license": "LGPL-3.0-or-later", 596 + "optional": true, 597 + "os": [ 598 + "linux" 599 + ], 600 + "funding": { 601 + "url": "https://opencollective.com/libvips" 602 + } 603 + }, 604 + "node_modules/@img/sharp-libvips-linux-s390x": { 605 + "version": "1.0.4", 606 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", 607 + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", 608 + "cpu": [ 609 + "s390x" 610 + ], 611 + "libc": [ 612 + "glibc" 613 + ], 614 + "license": "LGPL-3.0-or-later", 615 + "optional": true, 616 + "os": [ 617 + "linux" 618 + ], 619 + "funding": { 620 + "url": "https://opencollective.com/libvips" 621 + } 622 + }, 623 + "node_modules/@img/sharp-libvips-linux-x64": { 624 + "version": "1.0.4", 625 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", 626 + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", 627 + "cpu": [ 628 + "x64" 629 + ], 630 + "libc": [ 631 + "glibc" 632 + ], 633 + "license": "LGPL-3.0-or-later", 634 + "optional": true, 635 + "os": [ 636 + "linux" 637 + ], 638 + "funding": { 639 + "url": "https://opencollective.com/libvips" 640 + } 641 + }, 642 + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { 643 + "version": "1.0.4", 644 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", 645 + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", 646 + "cpu": [ 647 + "arm64" 648 + ], 649 + "libc": [ 650 + "musl" 651 + ], 652 + "license": "LGPL-3.0-or-later", 653 + "optional": true, 654 + "os": [ 655 + "linux" 656 + ], 657 + "funding": { 658 + "url": "https://opencollective.com/libvips" 659 + } 660 + }, 661 + "node_modules/@img/sharp-libvips-linuxmusl-x64": { 662 + "version": "1.0.4", 663 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", 664 + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", 665 + "cpu": [ 666 + "x64" 667 + ], 668 + "libc": [ 669 + "musl" 670 + ], 671 + "license": "LGPL-3.0-or-later", 672 + "optional": true, 673 + "os": [ 674 + "linux" 675 + ], 676 + "funding": { 677 + "url": "https://opencollective.com/libvips" 678 + } 679 + }, 680 + "node_modules/@img/sharp-linux-arm": { 681 + "version": "0.33.5", 682 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", 683 + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", 684 + "cpu": [ 685 + "arm" 686 + ], 687 + "libc": [ 688 + "glibc" 689 + ], 690 + "license": "Apache-2.0", 691 + "optional": true, 692 + "os": [ 693 + "linux" 694 + ], 695 + "engines": { 696 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 697 + }, 698 + "funding": { 699 + "url": "https://opencollective.com/libvips" 700 + }, 701 + "optionalDependencies": { 702 + "@img/sharp-libvips-linux-arm": "1.0.5" 703 + } 704 + }, 705 + "node_modules/@img/sharp-linux-arm64": { 706 + "version": "0.33.5", 707 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", 708 + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", 709 + "cpu": [ 710 + "arm64" 711 + ], 712 + "libc": [ 713 + "glibc" 714 + ], 715 + "license": "Apache-2.0", 716 + "optional": true, 717 + "os": [ 718 + "linux" 719 + ], 720 + "engines": { 721 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 722 + }, 723 + "funding": { 724 + "url": "https://opencollective.com/libvips" 725 + }, 726 + "optionalDependencies": { 727 + "@img/sharp-libvips-linux-arm64": "1.0.4" 728 + } 729 + }, 730 + "node_modules/@img/sharp-linux-s390x": { 731 + "version": "0.33.5", 732 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", 733 + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", 734 + "cpu": [ 735 + "s390x" 736 + ], 737 + "libc": [ 738 + "glibc" 739 + ], 740 + "license": "Apache-2.0", 741 + "optional": true, 742 + "os": [ 743 + "linux" 744 + ], 745 + "engines": { 746 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 747 + }, 748 + "funding": { 749 + "url": "https://opencollective.com/libvips" 750 + }, 751 + "optionalDependencies": { 752 + "@img/sharp-libvips-linux-s390x": "1.0.4" 753 + } 754 + }, 755 + "node_modules/@img/sharp-linux-x64": { 756 + "version": "0.33.5", 757 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", 758 + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", 759 + "cpu": [ 760 + "x64" 761 + ], 762 + "libc": [ 763 + "glibc" 764 + ], 765 + "license": "Apache-2.0", 766 + "optional": true, 767 + "os": [ 768 + "linux" 769 + ], 770 + "engines": { 771 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 772 + }, 773 + "funding": { 774 + "url": "https://opencollective.com/libvips" 775 + }, 776 + "optionalDependencies": { 777 + "@img/sharp-libvips-linux-x64": "1.0.4" 778 + } 779 + }, 780 + "node_modules/@img/sharp-linuxmusl-arm64": { 781 + "version": "0.33.5", 782 + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", 783 + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", 784 + "cpu": [ 785 + "arm64" 786 + ], 787 + "libc": [ 788 + "musl" 789 + ], 790 + "license": "Apache-2.0", 791 + "optional": true, 792 + "os": [ 793 + "linux" 794 + ], 795 + "engines": { 796 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 797 + }, 798 + "funding": { 799 + "url": "https://opencollective.com/libvips" 800 + }, 801 + "optionalDependencies": { 802 + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" 803 + } 804 + }, 805 + "node_modules/@img/sharp-linuxmusl-x64": { 806 + "version": "0.33.5", 807 + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", 808 + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", 809 + "cpu": [ 810 + "x64" 811 + ], 812 + "libc": [ 813 + "musl" 814 + ], 815 + "license": "Apache-2.0", 816 + "optional": true, 817 + "os": [ 818 + "linux" 819 + ], 820 + "engines": { 821 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 822 + }, 823 + "funding": { 824 + "url": "https://opencollective.com/libvips" 825 + }, 826 + "optionalDependencies": { 827 + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" 828 + } 829 + }, 830 + "node_modules/@img/sharp-wasm32": { 831 + "version": "0.33.5", 832 + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", 833 + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", 834 + "cpu": [ 835 + "wasm32" 836 + ], 837 + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", 838 + "optional": true, 839 + "dependencies": { 840 + "@emnapi/runtime": "^1.2.0" 841 + }, 842 + "engines": { 843 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 844 + }, 845 + "funding": { 846 + "url": "https://opencollective.com/libvips" 847 + } 848 + }, 849 + "node_modules/@img/sharp-win32-ia32": { 850 + "version": "0.33.5", 851 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", 852 + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", 853 + "cpu": [ 854 + "ia32" 855 + ], 856 + "license": "Apache-2.0 AND LGPL-3.0-or-later", 857 + "optional": true, 858 + "os": [ 859 + "win32" 860 + ], 861 + "engines": { 862 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 863 + }, 864 + "funding": { 865 + "url": "https://opencollective.com/libvips" 866 + } 867 + }, 868 + "node_modules/@img/sharp-win32-x64": { 869 + "version": "0.33.5", 870 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", 871 + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", 872 + "cpu": [ 873 + "x64" 874 + ], 875 + "license": "Apache-2.0 AND LGPL-3.0-or-later", 876 + "optional": true, 877 + "os": [ 878 + "win32" 879 + ], 880 + "engines": { 881 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 882 + }, 883 + "funding": { 884 + "url": "https://opencollective.com/libvips" 885 + } 886 + }, 887 + "node_modules/@resvg/resvg-js": { 888 + "version": "2.6.2", 889 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.6.2.tgz", 890 + "integrity": "sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==", 891 + "license": "MPL-2.0", 892 + "engines": { 893 + "node": ">= 10" 894 + }, 895 + "optionalDependencies": { 896 + "@resvg/resvg-js-android-arm-eabi": "2.6.2", 897 + "@resvg/resvg-js-android-arm64": "2.6.2", 898 + "@resvg/resvg-js-darwin-arm64": "2.6.2", 899 + "@resvg/resvg-js-darwin-x64": "2.6.2", 900 + "@resvg/resvg-js-linux-arm-gnueabihf": "2.6.2", 901 + "@resvg/resvg-js-linux-arm64-gnu": "2.6.2", 902 + "@resvg/resvg-js-linux-arm64-musl": "2.6.2", 903 + "@resvg/resvg-js-linux-x64-gnu": "2.6.2", 904 + "@resvg/resvg-js-linux-x64-musl": "2.6.2", 905 + "@resvg/resvg-js-win32-arm64-msvc": "2.6.2", 906 + "@resvg/resvg-js-win32-ia32-msvc": "2.6.2", 907 + "@resvg/resvg-js-win32-x64-msvc": "2.6.2" 908 + } 909 + }, 910 + "node_modules/@resvg/resvg-js-android-arm-eabi": { 911 + "version": "2.6.2", 912 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.6.2.tgz", 913 + "integrity": "sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==", 914 + "cpu": [ 915 + "arm" 916 + ], 917 + "license": "MPL-2.0", 918 + "optional": true, 919 + "os": [ 920 + "android" 921 + ], 922 + "engines": { 923 + "node": ">= 10" 924 + } 925 + }, 926 + "node_modules/@resvg/resvg-js-android-arm64": { 927 + "version": "2.6.2", 928 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.6.2.tgz", 929 + "integrity": "sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==", 930 + "cpu": [ 931 + "arm64" 932 + ], 933 + "license": "MPL-2.0", 934 + "optional": true, 935 + "os": [ 936 + "android" 937 + ], 938 + "engines": { 939 + "node": ">= 10" 940 + } 941 + }, 942 + "node_modules/@resvg/resvg-js-darwin-arm64": { 943 + "version": "2.6.2", 944 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.6.2.tgz", 945 + "integrity": "sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==", 946 + "cpu": [ 947 + "arm64" 948 + ], 949 + "license": "MPL-2.0", 950 + "optional": true, 951 + "os": [ 952 + "darwin" 953 + ], 954 + "engines": { 955 + "node": ">= 10" 956 + } 957 + }, 958 + "node_modules/@resvg/resvg-js-darwin-x64": { 959 + "version": "2.6.2", 960 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.6.2.tgz", 961 + "integrity": "sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==", 962 + "cpu": [ 963 + "x64" 964 + ], 965 + "license": "MPL-2.0", 966 + "optional": true, 967 + "os": [ 968 + "darwin" 969 + ], 970 + "engines": { 971 + "node": ">= 10" 972 + } 973 + }, 974 + "node_modules/@resvg/resvg-js-linux-arm-gnueabihf": { 975 + "version": "2.6.2", 976 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.6.2.tgz", 977 + "integrity": "sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==", 978 + "cpu": [ 979 + "arm" 980 + ], 981 + "license": "MPL-2.0", 982 + "optional": true, 983 + "os": [ 984 + "linux" 985 + ], 986 + "engines": { 987 + "node": ">= 10" 988 + } 989 + }, 990 + "node_modules/@resvg/resvg-js-linux-arm64-gnu": { 991 + "version": "2.6.2", 992 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.6.2.tgz", 993 + "integrity": "sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==", 994 + "cpu": [ 995 + "arm64" 996 + ], 997 + "libc": [ 998 + "glibc" 999 + ], 1000 + "license": "MPL-2.0", 1001 + "optional": true, 1002 + "os": [ 1003 + "linux" 1004 + ], 1005 + "engines": { 1006 + "node": ">= 10" 1007 + } 1008 + }, 1009 + "node_modules/@resvg/resvg-js-linux-arm64-musl": { 1010 + "version": "2.6.2", 1011 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.6.2.tgz", 1012 + "integrity": "sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==", 1013 + "cpu": [ 1014 + "arm64" 1015 + ], 1016 + "libc": [ 1017 + "musl" 1018 + ], 1019 + "license": "MPL-2.0", 1020 + "optional": true, 1021 + "os": [ 1022 + "linux" 1023 + ], 1024 + "engines": { 1025 + "node": ">= 10" 1026 + } 1027 + }, 1028 + "node_modules/@resvg/resvg-js-linux-x64-gnu": { 1029 + "version": "2.6.2", 1030 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.6.2.tgz", 1031 + "integrity": "sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==", 1032 + "cpu": [ 1033 + "x64" 1034 + ], 1035 + "libc": [ 1036 + "glibc" 1037 + ], 1038 + "license": "MPL-2.0", 1039 + "optional": true, 1040 + "os": [ 1041 + "linux" 1042 + ], 1043 + "engines": { 1044 + "node": ">= 10" 1045 + } 1046 + }, 1047 + "node_modules/@resvg/resvg-js-linux-x64-musl": { 1048 + "version": "2.6.2", 1049 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.6.2.tgz", 1050 + "integrity": "sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==", 1051 + "cpu": [ 1052 + "x64" 1053 + ], 1054 + "libc": [ 1055 + "musl" 1056 + ], 1057 + "license": "MPL-2.0", 1058 + "optional": true, 1059 + "os": [ 1060 + "linux" 1061 + ], 1062 + "engines": { 1063 + "node": ">= 10" 1064 + } 1065 + }, 1066 + "node_modules/@resvg/resvg-js-win32-arm64-msvc": { 1067 + "version": "2.6.2", 1068 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.6.2.tgz", 1069 + "integrity": "sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==", 1070 + "cpu": [ 1071 + "arm64" 1072 + ], 1073 + "license": "MPL-2.0", 1074 + "optional": true, 1075 + "os": [ 1076 + "win32" 1077 + ], 1078 + "engines": { 1079 + "node": ">= 10" 1080 + } 1081 + }, 1082 + "node_modules/@resvg/resvg-js-win32-ia32-msvc": { 1083 + "version": "2.6.2", 1084 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.6.2.tgz", 1085 + "integrity": "sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==", 1086 + "cpu": [ 1087 + "ia32" 1088 + ], 1089 + "license": "MPL-2.0", 1090 + "optional": true, 1091 + "os": [ 1092 + "win32" 1093 + ], 1094 + "engines": { 1095 + "node": ">= 10" 1096 + } 1097 + }, 1098 + "node_modules/@resvg/resvg-js-win32-x64-msvc": { 1099 + "version": "2.6.2", 1100 + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.6.2.tgz", 1101 + "integrity": "sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==", 1102 + "cpu": [ 1103 + "x64" 1104 + ], 1105 + "license": "MPL-2.0", 1106 + "optional": true, 1107 + "os": [ 1108 + "win32" 1109 + ], 1110 + "engines": { 1111 + "node": ">= 10" 1112 + } 1113 + }, 1114 + "node_modules/@types/d3-color": { 1115 + "version": "3.1.3", 1116 + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", 1117 + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", 1118 + "dev": true, 1119 + "license": "MIT" 1120 + }, 1121 + "node_modules/@types/d3-drag": { 1122 + "version": "3.0.7", 1123 + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", 1124 + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", 1125 + "dev": true, 1126 + "license": "MIT", 1127 + "dependencies": { 1128 + "@types/d3-selection": "*" 1129 + } 1130 + }, 1131 + "node_modules/@types/d3-force": { 1132 + "version": "3.0.10", 1133 + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", 1134 + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", 1135 + "dev": true, 1136 + "license": "MIT" 1137 + }, 1138 + "node_modules/@types/d3-interpolate": { 1139 + "version": "3.0.4", 1140 + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", 1141 + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", 1142 + "dev": true, 1143 + "license": "MIT", 1144 + "dependencies": { 1145 + "@types/d3-color": "*" 1146 + } 1147 + }, 1148 + "node_modules/@types/d3-selection": { 1149 + "version": "3.0.11", 1150 + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", 1151 + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", 1152 + "dev": true, 1153 + "license": "MIT" 1154 + }, 1155 + "node_modules/@types/d3-zoom": { 1156 + "version": "3.0.8", 1157 + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", 1158 + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", 1159 + "dev": true, 1160 + "license": "MIT", 1161 + "dependencies": { 1162 + "@types/d3-interpolate": "*", 1163 + "@types/d3-selection": "*" 1164 + } 1165 + }, 1166 + "node_modules/@types/js-yaml": { 1167 + "version": "4.0.9", 1168 + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", 1169 + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", 1170 + "dev": true, 1171 + "license": "MIT" 1172 + }, 1173 + "node_modules/@types/node": { 1174 + "version": "20.19.39", 1175 + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", 1176 + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", 1177 + "dev": true, 1178 + "license": "MIT", 1179 + "dependencies": { 1180 + "undici-types": "~6.21.0" 1181 + } 1182 + }, 1183 + "node_modules/argparse": { 1184 + "version": "2.0.1", 1185 + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 1186 + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 1187 + "license": "Python-2.0" 1188 + }, 1189 + "node_modules/color": { 1190 + "version": "4.2.3", 1191 + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", 1192 + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", 1193 + "license": "MIT", 1194 + "dependencies": { 1195 + "color-convert": "^2.0.1", 1196 + "color-string": "^1.9.0" 1197 + }, 1198 + "engines": { 1199 + "node": ">=12.5.0" 1200 + } 1201 + }, 1202 + "node_modules/color-convert": { 1203 + "version": "2.0.1", 1204 + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 1205 + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 1206 + "license": "MIT", 1207 + "dependencies": { 1208 + "color-name": "~1.1.4" 1209 + }, 1210 + "engines": { 1211 + "node": ">=7.0.0" 1212 + } 1213 + }, 1214 + "node_modules/color-name": { 1215 + "version": "1.1.4", 1216 + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 1217 + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 1218 + "license": "MIT" 1219 + }, 1220 + "node_modules/color-string": { 1221 + "version": "1.9.1", 1222 + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", 1223 + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", 1224 + "license": "MIT", 1225 + "dependencies": { 1226 + "color-name": "^1.0.0", 1227 + "simple-swizzle": "^0.2.2" 1228 + } 1229 + }, 1230 + "node_modules/commander": { 1231 + "version": "12.1.0", 1232 + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", 1233 + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", 1234 + "license": "MIT", 1235 + "engines": { 1236 + "node": ">=18" 1237 + } 1238 + }, 1239 + "node_modules/d3-color": { 1240 + "version": "3.1.0", 1241 + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", 1242 + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", 1243 + "license": "ISC", 1244 + "engines": { 1245 + "node": ">=12" 1246 + } 1247 + }, 1248 + "node_modules/d3-dispatch": { 1249 + "version": "3.0.1", 1250 + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", 1251 + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", 1252 + "license": "ISC", 1253 + "engines": { 1254 + "node": ">=12" 1255 + } 1256 + }, 1257 + "node_modules/d3-drag": { 1258 + "version": "3.0.0", 1259 + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", 1260 + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", 1261 + "license": "ISC", 1262 + "dependencies": { 1263 + "d3-dispatch": "1 - 3", 1264 + "d3-selection": "3" 1265 + }, 1266 + "engines": { 1267 + "node": ">=12" 1268 + } 1269 + }, 1270 + "node_modules/d3-ease": { 1271 + "version": "3.0.1", 1272 + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", 1273 + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", 1274 + "license": "BSD-3-Clause", 1275 + "engines": { 1276 + "node": ">=12" 1277 + } 1278 + }, 1279 + "node_modules/d3-force": { 1280 + "version": "3.0.0", 1281 + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", 1282 + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", 1283 + "license": "ISC", 1284 + "dependencies": { 1285 + "d3-dispatch": "1 - 3", 1286 + "d3-quadtree": "1 - 3", 1287 + "d3-timer": "1 - 3" 1288 + }, 1289 + "engines": { 1290 + "node": ">=12" 1291 + } 1292 + }, 1293 + "node_modules/d3-interpolate": { 1294 + "version": "3.0.1", 1295 + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", 1296 + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", 1297 + "license": "ISC", 1298 + "dependencies": { 1299 + "d3-color": "1 - 3" 1300 + }, 1301 + "engines": { 1302 + "node": ">=12" 1303 + } 1304 + }, 1305 + "node_modules/d3-quadtree": { 1306 + "version": "3.0.1", 1307 + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", 1308 + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", 1309 + "license": "ISC", 1310 + "engines": { 1311 + "node": ">=12" 1312 + } 1313 + }, 1314 + "node_modules/d3-selection": { 1315 + "version": "3.0.0", 1316 + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", 1317 + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", 1318 + "license": "ISC", 1319 + "engines": { 1320 + "node": ">=12" 1321 + } 1322 + }, 1323 + "node_modules/d3-timer": { 1324 + "version": "3.0.1", 1325 + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", 1326 + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", 1327 + "license": "ISC", 1328 + "engines": { 1329 + "node": ">=12" 1330 + } 1331 + }, 1332 + "node_modules/d3-transition": { 1333 + "version": "3.0.1", 1334 + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", 1335 + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", 1336 + "license": "ISC", 1337 + "dependencies": { 1338 + "d3-color": "1 - 3", 1339 + "d3-dispatch": "1 - 3", 1340 + "d3-ease": "1 - 3", 1341 + "d3-interpolate": "1 - 3", 1342 + "d3-timer": "1 - 3" 1343 + }, 1344 + "engines": { 1345 + "node": ">=12" 1346 + }, 1347 + "peerDependencies": { 1348 + "d3-selection": "2 - 3" 1349 + } 1350 + }, 1351 + "node_modules/d3-zoom": { 1352 + "version": "3.0.0", 1353 + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", 1354 + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", 1355 + "license": "ISC", 1356 + "dependencies": { 1357 + "d3-dispatch": "1 - 3", 1358 + "d3-drag": "2 - 3", 1359 + "d3-interpolate": "1 - 3", 1360 + "d3-selection": "2 - 3", 1361 + "d3-transition": "2 - 3" 1362 + }, 1363 + "engines": { 1364 + "node": ">=12" 1365 + } 1366 + }, 1367 + "node_modules/detect-libc": { 1368 + "version": "2.1.2", 1369 + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", 1370 + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", 1371 + "license": "Apache-2.0", 1372 + "engines": { 1373 + "node": ">=8" 1374 + } 1375 + }, 1376 + "node_modules/esbuild": { 1377 + "version": "0.25.12", 1378 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", 1379 + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", 1380 + "dev": true, 1381 + "hasInstallScript": true, 1382 + "license": "MIT", 1383 + "bin": { 1384 + "esbuild": "bin/esbuild" 1385 + }, 1386 + "engines": { 1387 + "node": ">=18" 1388 + }, 1389 + "optionalDependencies": { 1390 + "@esbuild/aix-ppc64": "0.25.12", 1391 + "@esbuild/android-arm": "0.25.12", 1392 + "@esbuild/android-arm64": "0.25.12", 1393 + "@esbuild/android-x64": "0.25.12", 1394 + "@esbuild/darwin-arm64": "0.25.12", 1395 + "@esbuild/darwin-x64": "0.25.12", 1396 + "@esbuild/freebsd-arm64": "0.25.12", 1397 + "@esbuild/freebsd-x64": "0.25.12", 1398 + "@esbuild/linux-arm": "0.25.12", 1399 + "@esbuild/linux-arm64": "0.25.12", 1400 + "@esbuild/linux-ia32": "0.25.12", 1401 + "@esbuild/linux-loong64": "0.25.12", 1402 + "@esbuild/linux-mips64el": "0.25.12", 1403 + "@esbuild/linux-ppc64": "0.25.12", 1404 + "@esbuild/linux-riscv64": "0.25.12", 1405 + "@esbuild/linux-s390x": "0.25.12", 1406 + "@esbuild/linux-x64": "0.25.12", 1407 + "@esbuild/netbsd-arm64": "0.25.12", 1408 + "@esbuild/netbsd-x64": "0.25.12", 1409 + "@esbuild/openbsd-arm64": "0.25.12", 1410 + "@esbuild/openbsd-x64": "0.25.12", 1411 + "@esbuild/openharmony-arm64": "0.25.12", 1412 + "@esbuild/sunos-x64": "0.25.12", 1413 + "@esbuild/win32-arm64": "0.25.12", 1414 + "@esbuild/win32-ia32": "0.25.12", 1415 + "@esbuild/win32-x64": "0.25.12" 1416 + } 1417 + }, 1418 + "node_modules/fsevents": { 1419 + "version": "2.3.3", 1420 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1421 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1422 + "dev": true, 1423 + "hasInstallScript": true, 1424 + "license": "MIT", 1425 + "optional": true, 1426 + "os": [ 1427 + "darwin" 1428 + ], 1429 + "engines": { 1430 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1431 + } 1432 + }, 1433 + "node_modules/get-tsconfig": { 1434 + "version": "4.13.7", 1435 + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", 1436 + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", 1437 + "dev": true, 1438 + "license": "MIT", 1439 + "dependencies": { 1440 + "resolve-pkg-maps": "^1.0.0" 1441 + }, 1442 + "funding": { 1443 + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" 1444 + } 1445 + }, 1446 + "node_modules/is-arrayish": { 1447 + "version": "0.3.4", 1448 + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", 1449 + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", 1450 + "license": "MIT" 1451 + }, 1452 + "node_modules/js-yaml": { 1453 + "version": "4.1.1", 1454 + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", 1455 + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", 1456 + "license": "MIT", 1457 + "dependencies": { 1458 + "argparse": "^2.0.1" 1459 + }, 1460 + "bin": { 1461 + "js-yaml": "bin/js-yaml.js" 1462 + } 1463 + }, 1464 + "node_modules/resolve-pkg-maps": { 1465 + "version": "1.0.0", 1466 + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", 1467 + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", 1468 + "dev": true, 1469 + "license": "MIT", 1470 + "funding": { 1471 + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" 1472 + } 1473 + }, 1474 + "node_modules/semver": { 1475 + "version": "7.7.4", 1476 + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", 1477 + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", 1478 + "license": "ISC", 1479 + "bin": { 1480 + "semver": "bin/semver.js" 1481 + }, 1482 + "engines": { 1483 + "node": ">=10" 1484 + } 1485 + }, 1486 + "node_modules/sharp": { 1487 + "version": "0.33.5", 1488 + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", 1489 + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", 1490 + "hasInstallScript": true, 1491 + "license": "Apache-2.0", 1492 + "dependencies": { 1493 + "color": "^4.2.3", 1494 + "detect-libc": "^2.0.3", 1495 + "semver": "^7.6.3" 1496 + }, 1497 + "engines": { 1498 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1499 + }, 1500 + "funding": { 1501 + "url": "https://opencollective.com/libvips" 1502 + }, 1503 + "optionalDependencies": { 1504 + "@img/sharp-darwin-arm64": "0.33.5", 1505 + "@img/sharp-darwin-x64": "0.33.5", 1506 + "@img/sharp-libvips-darwin-arm64": "1.0.4", 1507 + "@img/sharp-libvips-darwin-x64": "1.0.4", 1508 + "@img/sharp-libvips-linux-arm": "1.0.5", 1509 + "@img/sharp-libvips-linux-arm64": "1.0.4", 1510 + "@img/sharp-libvips-linux-s390x": "1.0.4", 1511 + "@img/sharp-libvips-linux-x64": "1.0.4", 1512 + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", 1513 + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", 1514 + "@img/sharp-linux-arm": "0.33.5", 1515 + "@img/sharp-linux-arm64": "0.33.5", 1516 + "@img/sharp-linux-s390x": "0.33.5", 1517 + "@img/sharp-linux-x64": "0.33.5", 1518 + "@img/sharp-linuxmusl-arm64": "0.33.5", 1519 + "@img/sharp-linuxmusl-x64": "0.33.5", 1520 + "@img/sharp-wasm32": "0.33.5", 1521 + "@img/sharp-win32-ia32": "0.33.5", 1522 + "@img/sharp-win32-x64": "0.33.5" 1523 + } 1524 + }, 1525 + "node_modules/simple-swizzle": { 1526 + "version": "0.2.4", 1527 + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", 1528 + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", 1529 + "license": "MIT", 1530 + "dependencies": { 1531 + "is-arrayish": "^0.3.1" 1532 + } 1533 + }, 1534 + "node_modules/tslib": { 1535 + "version": "2.8.1", 1536 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 1537 + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 1538 + "license": "0BSD", 1539 + "optional": true 1540 + }, 1541 + "node_modules/tsx": { 1542 + "version": "4.21.0", 1543 + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", 1544 + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", 1545 + "dev": true, 1546 + "license": "MIT", 1547 + "dependencies": { 1548 + "esbuild": "~0.27.0", 1549 + "get-tsconfig": "^4.7.5" 1550 + }, 1551 + "bin": { 1552 + "tsx": "dist/cli.mjs" 1553 + }, 1554 + "engines": { 1555 + "node": ">=18.0.0" 1556 + }, 1557 + "optionalDependencies": { 1558 + "fsevents": "~2.3.3" 1559 + } 1560 + }, 1561 + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { 1562 + "version": "0.27.7", 1563 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", 1564 + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", 1565 + "cpu": [ 1566 + "ppc64" 1567 + ], 1568 + "dev": true, 1569 + "license": "MIT", 1570 + "optional": true, 1571 + "os": [ 1572 + "aix" 1573 + ], 1574 + "engines": { 1575 + "node": ">=18" 1576 + } 1577 + }, 1578 + "node_modules/tsx/node_modules/@esbuild/android-arm": { 1579 + "version": "0.27.7", 1580 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", 1581 + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", 1582 + "cpu": [ 1583 + "arm" 1584 + ], 1585 + "dev": true, 1586 + "license": "MIT", 1587 + "optional": true, 1588 + "os": [ 1589 + "android" 1590 + ], 1591 + "engines": { 1592 + "node": ">=18" 1593 + } 1594 + }, 1595 + "node_modules/tsx/node_modules/@esbuild/android-arm64": { 1596 + "version": "0.27.7", 1597 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", 1598 + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", 1599 + "cpu": [ 1600 + "arm64" 1601 + ], 1602 + "dev": true, 1603 + "license": "MIT", 1604 + "optional": true, 1605 + "os": [ 1606 + "android" 1607 + ], 1608 + "engines": { 1609 + "node": ">=18" 1610 + } 1611 + }, 1612 + "node_modules/tsx/node_modules/@esbuild/android-x64": { 1613 + "version": "0.27.7", 1614 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", 1615 + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", 1616 + "cpu": [ 1617 + "x64" 1618 + ], 1619 + "dev": true, 1620 + "license": "MIT", 1621 + "optional": true, 1622 + "os": [ 1623 + "android" 1624 + ], 1625 + "engines": { 1626 + "node": ">=18" 1627 + } 1628 + }, 1629 + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { 1630 + "version": "0.27.7", 1631 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", 1632 + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", 1633 + "cpu": [ 1634 + "arm64" 1635 + ], 1636 + "dev": true, 1637 + "license": "MIT", 1638 + "optional": true, 1639 + "os": [ 1640 + "darwin" 1641 + ], 1642 + "engines": { 1643 + "node": ">=18" 1644 + } 1645 + }, 1646 + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { 1647 + "version": "0.27.7", 1648 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", 1649 + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", 1650 + "cpu": [ 1651 + "x64" 1652 + ], 1653 + "dev": true, 1654 + "license": "MIT", 1655 + "optional": true, 1656 + "os": [ 1657 + "darwin" 1658 + ], 1659 + "engines": { 1660 + "node": ">=18" 1661 + } 1662 + }, 1663 + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { 1664 + "version": "0.27.7", 1665 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", 1666 + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", 1667 + "cpu": [ 1668 + "arm64" 1669 + ], 1670 + "dev": true, 1671 + "license": "MIT", 1672 + "optional": true, 1673 + "os": [ 1674 + "freebsd" 1675 + ], 1676 + "engines": { 1677 + "node": ">=18" 1678 + } 1679 + }, 1680 + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { 1681 + "version": "0.27.7", 1682 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", 1683 + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", 1684 + "cpu": [ 1685 + "x64" 1686 + ], 1687 + "dev": true, 1688 + "license": "MIT", 1689 + "optional": true, 1690 + "os": [ 1691 + "freebsd" 1692 + ], 1693 + "engines": { 1694 + "node": ">=18" 1695 + } 1696 + }, 1697 + "node_modules/tsx/node_modules/@esbuild/linux-arm": { 1698 + "version": "0.27.7", 1699 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", 1700 + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", 1701 + "cpu": [ 1702 + "arm" 1703 + ], 1704 + "dev": true, 1705 + "license": "MIT", 1706 + "optional": true, 1707 + "os": [ 1708 + "linux" 1709 + ], 1710 + "engines": { 1711 + "node": ">=18" 1712 + } 1713 + }, 1714 + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { 1715 + "version": "0.27.7", 1716 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", 1717 + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", 1718 + "cpu": [ 1719 + "arm64" 1720 + ], 1721 + "dev": true, 1722 + "license": "MIT", 1723 + "optional": true, 1724 + "os": [ 1725 + "linux" 1726 + ], 1727 + "engines": { 1728 + "node": ">=18" 1729 + } 1730 + }, 1731 + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { 1732 + "version": "0.27.7", 1733 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", 1734 + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", 1735 + "cpu": [ 1736 + "ia32" 1737 + ], 1738 + "dev": true, 1739 + "license": "MIT", 1740 + "optional": true, 1741 + "os": [ 1742 + "linux" 1743 + ], 1744 + "engines": { 1745 + "node": ">=18" 1746 + } 1747 + }, 1748 + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { 1749 + "version": "0.27.7", 1750 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", 1751 + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", 1752 + "cpu": [ 1753 + "loong64" 1754 + ], 1755 + "dev": true, 1756 + "license": "MIT", 1757 + "optional": true, 1758 + "os": [ 1759 + "linux" 1760 + ], 1761 + "engines": { 1762 + "node": ">=18" 1763 + } 1764 + }, 1765 + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { 1766 + "version": "0.27.7", 1767 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", 1768 + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", 1769 + "cpu": [ 1770 + "mips64el" 1771 + ], 1772 + "dev": true, 1773 + "license": "MIT", 1774 + "optional": true, 1775 + "os": [ 1776 + "linux" 1777 + ], 1778 + "engines": { 1779 + "node": ">=18" 1780 + } 1781 + }, 1782 + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { 1783 + "version": "0.27.7", 1784 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", 1785 + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", 1786 + "cpu": [ 1787 + "ppc64" 1788 + ], 1789 + "dev": true, 1790 + "license": "MIT", 1791 + "optional": true, 1792 + "os": [ 1793 + "linux" 1794 + ], 1795 + "engines": { 1796 + "node": ">=18" 1797 + } 1798 + }, 1799 + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { 1800 + "version": "0.27.7", 1801 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", 1802 + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", 1803 + "cpu": [ 1804 + "riscv64" 1805 + ], 1806 + "dev": true, 1807 + "license": "MIT", 1808 + "optional": true, 1809 + "os": [ 1810 + "linux" 1811 + ], 1812 + "engines": { 1813 + "node": ">=18" 1814 + } 1815 + }, 1816 + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { 1817 + "version": "0.27.7", 1818 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", 1819 + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", 1820 + "cpu": [ 1821 + "s390x" 1822 + ], 1823 + "dev": true, 1824 + "license": "MIT", 1825 + "optional": true, 1826 + "os": [ 1827 + "linux" 1828 + ], 1829 + "engines": { 1830 + "node": ">=18" 1831 + } 1832 + }, 1833 + "node_modules/tsx/node_modules/@esbuild/linux-x64": { 1834 + "version": "0.27.7", 1835 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", 1836 + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", 1837 + "cpu": [ 1838 + "x64" 1839 + ], 1840 + "dev": true, 1841 + "license": "MIT", 1842 + "optional": true, 1843 + "os": [ 1844 + "linux" 1845 + ], 1846 + "engines": { 1847 + "node": ">=18" 1848 + } 1849 + }, 1850 + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { 1851 + "version": "0.27.7", 1852 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", 1853 + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", 1854 + "cpu": [ 1855 + "arm64" 1856 + ], 1857 + "dev": true, 1858 + "license": "MIT", 1859 + "optional": true, 1860 + "os": [ 1861 + "netbsd" 1862 + ], 1863 + "engines": { 1864 + "node": ">=18" 1865 + } 1866 + }, 1867 + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { 1868 + "version": "0.27.7", 1869 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", 1870 + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", 1871 + "cpu": [ 1872 + "x64" 1873 + ], 1874 + "dev": true, 1875 + "license": "MIT", 1876 + "optional": true, 1877 + "os": [ 1878 + "netbsd" 1879 + ], 1880 + "engines": { 1881 + "node": ">=18" 1882 + } 1883 + }, 1884 + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { 1885 + "version": "0.27.7", 1886 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", 1887 + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", 1888 + "cpu": [ 1889 + "arm64" 1890 + ], 1891 + "dev": true, 1892 + "license": "MIT", 1893 + "optional": true, 1894 + "os": [ 1895 + "openbsd" 1896 + ], 1897 + "engines": { 1898 + "node": ">=18" 1899 + } 1900 + }, 1901 + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { 1902 + "version": "0.27.7", 1903 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", 1904 + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", 1905 + "cpu": [ 1906 + "x64" 1907 + ], 1908 + "dev": true, 1909 + "license": "MIT", 1910 + "optional": true, 1911 + "os": [ 1912 + "openbsd" 1913 + ], 1914 + "engines": { 1915 + "node": ">=18" 1916 + } 1917 + }, 1918 + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { 1919 + "version": "0.27.7", 1920 + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", 1921 + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", 1922 + "cpu": [ 1923 + "arm64" 1924 + ], 1925 + "dev": true, 1926 + "license": "MIT", 1927 + "optional": true, 1928 + "os": [ 1929 + "openharmony" 1930 + ], 1931 + "engines": { 1932 + "node": ">=18" 1933 + } 1934 + }, 1935 + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { 1936 + "version": "0.27.7", 1937 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", 1938 + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", 1939 + "cpu": [ 1940 + "x64" 1941 + ], 1942 + "dev": true, 1943 + "license": "MIT", 1944 + "optional": true, 1945 + "os": [ 1946 + "sunos" 1947 + ], 1948 + "engines": { 1949 + "node": ">=18" 1950 + } 1951 + }, 1952 + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { 1953 + "version": "0.27.7", 1954 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", 1955 + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", 1956 + "cpu": [ 1957 + "arm64" 1958 + ], 1959 + "dev": true, 1960 + "license": "MIT", 1961 + "optional": true, 1962 + "os": [ 1963 + "win32" 1964 + ], 1965 + "engines": { 1966 + "node": ">=18" 1967 + } 1968 + }, 1969 + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { 1970 + "version": "0.27.7", 1971 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", 1972 + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", 1973 + "cpu": [ 1974 + "ia32" 1975 + ], 1976 + "dev": true, 1977 + "license": "MIT", 1978 + "optional": true, 1979 + "os": [ 1980 + "win32" 1981 + ], 1982 + "engines": { 1983 + "node": ">=18" 1984 + } 1985 + }, 1986 + "node_modules/tsx/node_modules/@esbuild/win32-x64": { 1987 + "version": "0.27.7", 1988 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", 1989 + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", 1990 + "cpu": [ 1991 + "x64" 1992 + ], 1993 + "dev": true, 1994 + "license": "MIT", 1995 + "optional": true, 1996 + "os": [ 1997 + "win32" 1998 + ], 1999 + "engines": { 2000 + "node": ">=18" 2001 + } 2002 + }, 2003 + "node_modules/tsx/node_modules/esbuild": { 2004 + "version": "0.27.7", 2005 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", 2006 + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", 2007 + "dev": true, 2008 + "hasInstallScript": true, 2009 + "license": "MIT", 2010 + "bin": { 2011 + "esbuild": "bin/esbuild" 2012 + }, 2013 + "engines": { 2014 + "node": ">=18" 2015 + }, 2016 + "optionalDependencies": { 2017 + "@esbuild/aix-ppc64": "0.27.7", 2018 + "@esbuild/android-arm": "0.27.7", 2019 + "@esbuild/android-arm64": "0.27.7", 2020 + "@esbuild/android-x64": "0.27.7", 2021 + "@esbuild/darwin-arm64": "0.27.7", 2022 + "@esbuild/darwin-x64": "0.27.7", 2023 + "@esbuild/freebsd-arm64": "0.27.7", 2024 + "@esbuild/freebsd-x64": "0.27.7", 2025 + "@esbuild/linux-arm": "0.27.7", 2026 + "@esbuild/linux-arm64": "0.27.7", 2027 + "@esbuild/linux-ia32": "0.27.7", 2028 + "@esbuild/linux-loong64": "0.27.7", 2029 + "@esbuild/linux-mips64el": "0.27.7", 2030 + "@esbuild/linux-ppc64": "0.27.7", 2031 + "@esbuild/linux-riscv64": "0.27.7", 2032 + "@esbuild/linux-s390x": "0.27.7", 2033 + "@esbuild/linux-x64": "0.27.7", 2034 + "@esbuild/netbsd-arm64": "0.27.7", 2035 + "@esbuild/netbsd-x64": "0.27.7", 2036 + "@esbuild/openbsd-arm64": "0.27.7", 2037 + "@esbuild/openbsd-x64": "0.27.7", 2038 + "@esbuild/openharmony-arm64": "0.27.7", 2039 + "@esbuild/sunos-x64": "0.27.7", 2040 + "@esbuild/win32-arm64": "0.27.7", 2041 + "@esbuild/win32-ia32": "0.27.7", 2042 + "@esbuild/win32-x64": "0.27.7" 2043 + } 2044 + }, 2045 + "node_modules/typescript": { 2046 + "version": "5.9.3", 2047 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", 2048 + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", 2049 + "dev": true, 2050 + "license": "Apache-2.0", 2051 + "bin": { 2052 + "tsc": "bin/tsc", 2053 + "tsserver": "bin/tsserver" 2054 + }, 2055 + "engines": { 2056 + "node": ">=14.17" 2057 + } 2058 + }, 2059 + "node_modules/undici-types": { 2060 + "version": "6.21.0", 2061 + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", 2062 + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", 2063 + "dev": true, 2064 + "license": "MIT" 2065 + } 2066 + } 2067 + }
+34
package.json
··· 1 + { 2 + "name": "polymap", 3 + "version": "0.1.0", 4 + "description": "Generate interactive polycule relationship maps from YAML", 5 + "bin": { "polymap": "./dist/cli.js" }, 6 + "engines": { "node": ">=18.0.0" }, 7 + "scripts": { 8 + "build": "npm run build:client && npm run build:cli", 9 + "build:client": "tsx scripts/build-client.ts", 10 + "build:cli": "esbuild src/cli.ts --bundle --platform=node --format=cjs --outfile=dist/cli.js --external:@resvg/resvg-js --external:sharp --banner:js='#!/usr/bin/env node'", 11 + "dev": "tsx src/cli.ts" 12 + }, 13 + "dependencies": { 14 + "@resvg/resvg-js": "^2.6.0", 15 + "sharp": "^0.33.0", 16 + "commander": "^12.0.0", 17 + "d3-drag": "^3.0.0", 18 + "d3-force": "^3.0.0", 19 + "d3-selection": "^3.0.0", 20 + "d3-zoom": "^3.0.0", 21 + "js-yaml": "^4.1.0" 22 + }, 23 + "devDependencies": { 24 + "@types/d3-drag": "^3.0.7", 25 + "@types/d3-force": "^3.0.10", 26 + "@types/d3-selection": "^3.0.10", 27 + "@types/d3-zoom": "^3.0.8", 28 + "@types/js-yaml": "^4.0.9", 29 + "@types/node": "^20.0.0", 30 + "esbuild": "^0.25.0", 31 + "tsx": "^4.7.0", 32 + "typescript": "^5.4.0" 33 + } 34 + }
+44
scripts/build-client.ts
··· 1 + /** 2 + * Bundles src/client/graph.ts for browser use (IIFE, minified), 3 + * then writes the result as a TypeScript string constant to 4 + * src/generated/client-bundle.ts so the CLI can inline it into HTML output. 5 + * 6 + * Run: npx tsx scripts/build-client.ts 7 + */ 8 + import { buildSync } from 'esbuild'; 9 + import { writeFileSync, mkdirSync } from 'fs'; 10 + import { resolve } from 'path'; 11 + 12 + const root = resolve(process.cwd()); 13 + 14 + const result = buildSync({ 15 + entryPoints: [resolve(root, 'src/client/graph.ts')], 16 + bundle: true, 17 + format: 'iife', 18 + globalName: '_PM', 19 + minify: true, 20 + platform: 'browser', 21 + target: ['es2017'], 22 + write: false, 23 + define: { 24 + 'process.env.NODE_ENV': '"production"', 25 + }, 26 + logLevel: 'warning', 27 + }); 28 + 29 + if (result.errors.length > 0) { 30 + console.error('Client bundle errors:', result.errors); 31 + process.exit(1); 32 + } 33 + 34 + const bundle = result.outputFiles[0].text; 35 + 36 + const output = `// AUTO-GENERATED — do not edit manually. 37 + // Regenerate with: npm run build:client 38 + export const CLIENT_BUNDLE = ${JSON.stringify(bundle)}; 39 + `; 40 + 41 + mkdirSync(resolve(root, 'src/generated'), { recursive: true }); 42 + writeFileSync(resolve(root, 'src/generated/client-bundle.ts'), output, 'utf8'); 43 + 44 + console.log(`✓ Client bundle: ${(bundle.length / 1024).toFixed(1)} kB → src/generated/client-bundle.ts`);
+103
src/cli.ts
··· 1 + import { Command } from 'commander'; 2 + import { writeFileSync, mkdirSync } from 'fs'; 3 + import { resolve, join, basename, extname } from 'path'; 4 + import { parseConfig } from './parser.js'; 5 + import { simulate } from './simulate.js'; 6 + import { generateSVG } from './exporters/svg.js'; 7 + import { generatePNG } from './exporters/png.js'; 8 + import { generateStandaloneHTML, generateEmbedJS } from './exporters/html.js'; 9 + 10 + const program = new Command(); 11 + 12 + program 13 + .name('polymap') 14 + .description('Generate interactive polycule relationship maps from YAML') 15 + .version('0.1.0'); 16 + 17 + program 18 + .command('generate <input>') 19 + .description('Generate map from a YAML config file') 20 + .option('-o, --output <dir>', 'output directory', '.') 21 + .option( 22 + '-f, --format <formats>', 23 + 'comma-separated output formats: html,svg,png', 24 + 'html,svg,png' 25 + ) 26 + .option('-n, --name <prefix>', 'output filename prefix (default: input filename without extension)') 27 + .option('--width <pixels>', 'PNG output width in pixels', '1400') 28 + .option('--title <title>', 'HTML page title', 'Polycule Map') 29 + .option('--legend', 'render relationship legend on SVG/PNG exports', false) 30 + .option('--no-labels', 'hide edge label text on SVG/PNG exports') 31 + .option('--no-names', 'hide node name labels on SVG/PNG exports') 32 + .action(async (input: string, opts) => { 33 + const inputPath = resolve(input); 34 + const outputDir = resolve(opts.output as string); 35 + const formats = (opts.format as string).split(',').map(s => s.trim().toLowerCase()); 36 + const prefix = (opts.name as string | undefined) ?? basename(inputPath, extname(inputPath)); 37 + const pngWidth = parseInt(opts.width as string, 10) || 1400; 38 + const title = opts.title as string; 39 + const showLegend = Boolean(opts.legend); 40 + const showEdgeLabels = opts.labels !== false; 41 + const showNames = opts.names !== false; 42 + 43 + mkdirSync(outputDir, { recursive: true }); 44 + 45 + let data; 46 + try { 47 + data = parseConfig(inputPath); 48 + } catch (err) { 49 + console.error(`Error parsing config: ${(err as Error).message}`); 50 + process.exit(1); 51 + } 52 + 53 + console.log(`Loaded ${data.people.length} people, ${data.relationships.length} relationships`); 54 + 55 + // Run force simulation for static exports 56 + const needsSimulation = formats.includes('svg') || formats.includes('png'); 57 + let simResult: ReturnType<typeof simulate> | null = null; 58 + if (needsSimulation) { 59 + process.stdout.write('Running force simulation...'); 60 + simResult = simulate(data); 61 + process.stdout.write(' done\n'); 62 + } 63 + 64 + for (const fmt of formats) { 65 + switch (fmt) { 66 + case 'html': { 67 + const htmlPath = join(outputDir, `${prefix}.html`); 68 + const embedPath = join(outputDir, `${prefix}-embed.js`); 69 + writeFileSync(htmlPath, generateStandaloneHTML(data, { title }), 'utf8'); 70 + writeFileSync(embedPath, generateEmbedJS(data), 'utf8'); 71 + console.log(`HTML → ${htmlPath}`); 72 + console.log(`Embed → ${embedPath}`); 73 + break; 74 + } 75 + 76 + case 'svg': { 77 + const svgPath = join(outputDir, `${prefix}.svg`); 78 + const svgStr = await generateSVG(data, simResult!.nodes, simResult!.links, { showLegend, showEdgeLabels, showNames }); 79 + writeFileSync(svgPath, svgStr, 'utf8'); 80 + console.log(`SVG → ${svgPath}`); 81 + break; 82 + } 83 + 84 + case 'png': { 85 + const pngPath = join(outputDir, `${prefix}.png`); 86 + const pngBuf = await generatePNG(data, simResult!.nodes, simResult!.links, pngWidth, showLegend, showEdgeLabels, showNames); 87 + writeFileSync(pngPath, pngBuf); 88 + console.log(`PNG → ${pngPath}`); 89 + break; 90 + } 91 + 92 + default: 93 + console.warn(`Unknown format "${fmt}" — skipping`); 94 + } 95 + } 96 + 97 + console.log('Done.'); 98 + }); 99 + 100 + program.parseAsync(process.argv).catch(err => { 101 + console.error(err); 102 + process.exit(1); 103 + });
+698
src/client/graph.ts
··· 1 + // Browser-only. Bundled by esbuild — no Node.js APIs. 2 + import { forceSimulation, forceLink, forceManyBody, forceCenter, forceCollide } from 'd3-force'; 3 + import { select } from 'd3-selection'; 4 + import { zoom as d3zoom, zoomIdentity, type ZoomTransform } from 'd3-zoom'; 5 + import { drag as d3drag } from 'd3-drag'; 6 + import type { PolyculeData, Person, Relationship } from '../types.js'; 7 + import { RELATIONSHIP_STYLES, nodeColor, initials } from '../styles.js'; 8 + 9 + // ─── Types ──────────────────────────────────────────────────────────────────── 10 + 11 + interface NodeDatum extends Person { 12 + index?: number; 13 + x: number; 14 + y: number; 15 + vx: number; 16 + vy: number; 17 + fx: number | null; 18 + fy: number | null; 19 + connectionCount: number; 20 + } 21 + 22 + interface LinkDatum { 23 + source: NodeDatum; 24 + target: NodeDatum; 25 + relationship: Relationship; 26 + index?: number; 27 + } 28 + 29 + // ─── Constants ──────────────────────────────────────────────────────────────── 30 + 31 + const BASE_RADIUS = 28; 32 + const LABEL_OFFSET = 16; 33 + 34 + // ─── Theme helpers ──────────────────────────────────────────────────────────── 35 + 36 + function getThemeColors(dark: boolean) { 37 + return { 38 + bg: dark ? '#0d1117' : '#f0f4f8', 39 + grid: dark ? 'rgba(255,255,255,0.025)' : 'rgba(0,0,0,0.04)', 40 + text: dark ? '#e6edf3' : '#1c1e21', 41 + textMuted: dark ? '#8b949e' : '#65676b', 42 + nodeLabelBg: dark ? 'rgba(0,0,0,0.55)' : 'rgba(255,255,255,0.75)', 43 + edgeLabelBg: dark ? 'rgba(13,17,23,0.75)' : 'rgba(240,244,248,0.8)', 44 + panelBg: dark ? 'rgba(22,27,34,0.95)' : 'rgba(255,255,255,0.97)', 45 + legendBg: dark ? 'rgba(13,17,23,0.45)' : 'rgba(255,255,255,0.5)', 46 + panelBorder: dark ? 'rgba(48,54,61,0.9)' : 'rgba(208,215,222,0.9)', 47 + panelText: dark ? '#e6edf3' : '#1c1e21', 48 + panelMuted: dark ? '#8b949e' : '#65676b', 49 + btnBg: dark ? 'rgba(33,38,45,0.9)' : 'rgba(255,255,255,0.9)', 50 + btnBorder: dark ? 'rgba(48,54,61,0.8)' : 'rgba(208,215,222,0.8)', 51 + btnText: dark ? '#8b949e' : '#65676b', 52 + }; 53 + } 54 + 55 + // ─── CSS injection ──────────────────────────────────────────────────────────── 56 + 57 + function injectStyles(): void { 58 + if (document.getElementById('polymap-styles')) return; 59 + const style = document.createElement('style'); 60 + style.id = 'polymap-styles'; 61 + style.textContent = ` 62 + .polymap-wrap { position: relative; width: 100%; height: 100%; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } 63 + .polymap-wrap svg { display: block; width: 100%; height: 100%; cursor: grab; } 64 + .polymap-wrap svg:active { cursor: grabbing; } 65 + .polymap-node { cursor: pointer; } 66 + .polymap-node:hover .pm-halo { opacity: 0.35 !important; } 67 + .polymap-node:hover .pm-ring { stroke-width: 3 !important; } 68 + 69 + .pm-info-panel { 70 + position: absolute; 71 + min-width: 200px; 72 + max-width: 280px; 73 + border-radius: 12px; 74 + padding: 14px 16px; 75 + box-shadow: 0 8px 32px rgba(0,0,0,0.35); 76 + backdrop-filter: blur(12px); 77 + -webkit-backdrop-filter: blur(12px); 78 + border: 1px solid; 79 + pointer-events: auto; 80 + z-index: 100; 81 + transition: opacity 0.15s ease; 82 + } 83 + .pm-info-panel.hidden { opacity: 0; pointer-events: none; } 84 + .pm-info-header { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; } 85 + .pm-info-avatar { 86 + width: 40px; height: 40px; border-radius: 50%; flex-shrink: 0; 87 + display: flex; align-items: center; justify-content: center; 88 + font-size: 14px; font-weight: 700; color: #fff; 89 + background-size: cover; background-position: center; 90 + overflow: hidden; 91 + } 92 + .pm-info-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; } 93 + .pm-info-name { font-size: 15px; font-weight: 600; line-height: 1.2; } 94 + .pm-info-pronouns { font-size: 12px; margin-top: 1px; } 95 + .pm-info-close { 96 + margin-left: auto; background: none; border: none; cursor: pointer; 97 + font-size: 18px; line-height: 1; padding: 0 2px; opacity: 0.5; 98 + transition: opacity 0.1s; 99 + } 100 + .pm-info-close:hover { opacity: 1; } 101 + .pm-info-links { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; } 102 + .pm-info-link { 103 + font-size: 12px; padding: 3px 9px; border-radius: 20px; 104 + border: 1px solid; text-decoration: none; opacity: 0.85; 105 + transition: opacity 0.1s; 106 + } 107 + .pm-info-link:hover { opacity: 1; } 108 + 109 + .pm-controls { 110 + position: absolute; top: 12px; right: 12px; 111 + display: flex; flex-direction: column; gap: 6px; z-index: 50; 112 + } 113 + .pm-btn { 114 + border: 1px solid; border-radius: 8px; padding: 6px 12px; 115 + font-size: 12px; cursor: pointer; backdrop-filter: blur(8px); 116 + -webkit-backdrop-filter: blur(8px); transition: opacity 0.1s; 117 + white-space: nowrap; 118 + } 119 + .pm-btn:hover { opacity: 0.8; } 120 + 121 + .pm-legend { 122 + position: absolute; bottom: 12px; left: 12px; z-index: 50; 123 + border: 1px solid; border-radius: 10px; padding: 10px 14px; 124 + backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); 125 + min-width: 160px; 126 + } 127 + .pm-legend.hidden { display: none; } 128 + .pm-legend-title { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 8px; } 129 + .pm-legend-item { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; font-size: 12px; } 130 + .pm-legend-line { flex-shrink: 0; } 131 + `; 132 + document.head.appendChild(style); 133 + } 134 + 135 + // ─── Main export ───────────────────────────────────────────────────────────── 136 + 137 + export function init(container: HTMLElement, data: PolyculeData): void { 138 + injectStyles(); 139 + 140 + let isDark = data.settings.theme !== 'light'; 141 + let legendVisible = true; 142 + let labelsVisible = true; 143 + let namesVisible = true; 144 + let currentTransform: ZoomTransform = zoomIdentity; 145 + 146 + const wrap = document.createElement('div'); 147 + wrap.className = 'polymap-wrap'; 148 + container.appendChild(wrap); 149 + 150 + // ── Build node/link data ────────────────────────────────────────────────── 151 + 152 + const connectionCounts = new Map<string, number>(); 153 + data.people.forEach(p => connectionCounts.set(p.id, 0)); 154 + data.relationships.forEach(r => { 155 + connectionCounts.set(r.from, (connectionCounts.get(r.from) ?? 0) + 1); 156 + connectionCounts.set(r.to, (connectionCounts.get(r.to) ?? 0) + 1); 157 + }); 158 + 159 + const nodeCount = data.people.length; 160 + const nodes: NodeDatum[] = data.people.map((p, i) => { 161 + const angle = (i / nodeCount) * 2 * Math.PI; 162 + const r = 220; 163 + return { 164 + ...p, 165 + x: 480 + Math.cos(angle) * r, 166 + y: 360 + Math.sin(angle) * r, 167 + vx: 0, vy: 0, fx: null, fy: null, 168 + connectionCount: connectionCounts.get(p.id) ?? 0, 169 + }; 170 + }); 171 + 172 + const nodeById = new Map(nodes.map(n => [n.id, n])); 173 + 174 + const links: LinkDatum[] = data.relationships.map(r => ({ 175 + source: nodeById.get(r.from)!, 176 + target: nodeById.get(r.to)!, 177 + relationship: r, 178 + })); 179 + 180 + function nodeRadius(d: NodeDatum): number { 181 + if (data.settings.nodeScale === 'connections') { 182 + return BASE_RADIUS + d.connectionCount * 4; 183 + } 184 + return BASE_RADIUS; 185 + } 186 + 187 + // ── SVG scaffold ───────────────────────────────────────────────────────── 188 + 189 + const svgEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 190 + wrap.appendChild(svgEl); 191 + const svg = select(svgEl); 192 + 193 + const defs = svg.append('defs'); 194 + 195 + // Grid pattern 196 + const gridPat = defs.append('pattern') 197 + .attr('id', 'pm-grid') 198 + .attr('width', 40).attr('height', 40) 199 + .attr('patternUnits', 'userSpaceOnUse'); 200 + gridPat.append('path') 201 + .attr('d', 'M 40 0 L 0 0 0 40') 202 + .attr('fill', 'none') 203 + .attr('class', 'pm-grid-path') 204 + .attr('stroke-width', '1'); 205 + 206 + // Single clip path for all circular nodes (applied in local group space) 207 + defs.append('clipPath').attr('id', 'pm-node-clip') 208 + .append('circle').attr('r', BASE_RADIUS); 209 + 210 + // Glow filter for nodes 211 + const nodeGlow = defs.append('filter') 212 + .attr('id', 'pm-node-glow') 213 + .attr('x', '-60%').attr('y', '-60%') 214 + .attr('width', '220%').attr('height', '220%'); 215 + nodeGlow.append('feGaussianBlur') 216 + .attr('in', 'SourceGraphic').attr('stdDeviation', '5').attr('result', 'blur'); 217 + const nodeGlowMerge = nodeGlow.append('feMerge'); 218 + nodeGlowMerge.append('feMergeNode').attr('in', 'blur'); 219 + nodeGlowMerge.append('feMergeNode').attr('in', 'SourceGraphic'); 220 + 221 + // Glow filter for edges 222 + const edgeGlow = defs.append('filter') 223 + .attr('id', 'pm-edge-glow') 224 + .attr('x', '-40%').attr('y', '-40%') 225 + .attr('width', '180%').attr('height', '180%'); 226 + edgeGlow.append('feGaussianBlur') 227 + .attr('in', 'SourceGraphic').attr('stdDeviation', '2.5').attr('result', 'blur'); 228 + const edgeGlowMerge = edgeGlow.append('feMerge'); 229 + edgeGlowMerge.append('feMergeNode').attr('in', 'blur'); 230 + edgeGlowMerge.append('feMergeNode').attr('in', 'SourceGraphic'); 231 + 232 + // Background 233 + const bgRect = svg.append('rect') 234 + .attr('class', 'pm-bg') 235 + .attr('width', '100%').attr('height', '100%'); 236 + 237 + // Grid overlay 238 + svg.append('rect') 239 + .attr('class', 'pm-grid-rect') 240 + .attr('width', '100%').attr('height', '100%') 241 + .attr('fill', 'url(#pm-grid)'); 242 + 243 + // Main transform group (zoom target) 244 + const g = svg.append('g').attr('class', 'pm-graph-root'); 245 + 246 + const edgeGroup = g.append('g').attr('class', 'pm-edges'); 247 + const nodeGroup = g.append('g').attr('class', 'pm-nodes'); 248 + 249 + // ── Render edges ───────────────────────────────────────────────────────── 250 + 251 + const edgeGs = edgeGroup.selectAll<SVGGElement, LinkDatum>('g.pm-edge') 252 + .data(links).join('g').attr('class', 'pm-edge'); 253 + 254 + // Background line (for double-stroke style) 255 + const edgeBg = edgeGs.append('line') 256 + .attr('class', 'pm-edge-bg') 257 + .attr('stroke-linecap', 'round'); 258 + 259 + const edgeLine = edgeGs.append('line') 260 + .attr('class', 'pm-edge-line') 261 + .attr('stroke-linecap', 'round'); 262 + 263 + // Edge label group (rect + text) 264 + const edgeLabelG = edgeGs.append('g').attr('class', 'pm-edge-label'); 265 + edgeLabelG.append('rect') 266 + .attr('rx', 4).attr('ry', 4) 267 + .attr('x', -28).attr('y', -8) 268 + .attr('width', 56).attr('height', 14); 269 + edgeLabelG.append('text') 270 + .attr('text-anchor', 'middle') 271 + .attr('dominant-baseline', 'central') 272 + .attr('y', 0) 273 + .attr('font-size', '9') 274 + .attr('font-family', '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif') 275 + .attr('font-weight', '500'); 276 + 277 + // Apply edge styles — set dynamic label rect width based on text length 278 + edgeGs.each(function(d) { 279 + const s = RELATIONSHIP_STYLES[d.relationship.type]; 280 + const labelStr = d.relationship.label ?? s.label; 281 + const lw = Math.max(40, labelStr.length * 5.5 + 12); 282 + select(this).select('rect').attr('x', -lw / 2).attr('width', lw); 283 + 284 + const g = select(this); 285 + const bg = g.select<SVGLineElement>('.pm-edge-bg'); 286 + const line = g.select<SVGLineElement>('.pm-edge-line'); 287 + const labelG = g.select<SVGGElement>('.pm-edge-label'); 288 + const labelText = labelG.select('text'); 289 + 290 + if (s.double) { 291 + bg.attr('stroke-width', s.width * 2.6).style('visibility', 'visible'); 292 + } else { 293 + bg.style('visibility', 'hidden'); 294 + } 295 + 296 + line 297 + .attr('stroke', s.color) 298 + .attr('stroke-width', s.width) 299 + .attr('stroke-dasharray', s.dashArray || null) 300 + .attr('opacity', '0.85') 301 + .attr('filter', 'url(#pm-edge-glow)'); 302 + 303 + labelText.text(labelStr).attr('fill', s.color); 304 + }); 305 + 306 + // ── Render nodes ───────────────────────────────────────────────────────── 307 + 308 + const nodeGs = nodeGroup.selectAll<SVGGElement, NodeDatum>('g.polymap-node') 309 + .data(nodes).join('g') 310 + .attr('class', 'polymap-node') 311 + .attr('data-id', d => d.id); 312 + 313 + nodeGs.each(function(d) { 314 + const r = nodeRadius(d); 315 + const color = nodeColor(d.id, d.color); 316 + const g = select(this); 317 + 318 + // Outer halo glow 319 + g.append('circle') 320 + .attr('class', 'pm-halo') 321 + .attr('r', r + 10) 322 + .attr('fill', color) 323 + .attr('opacity', 0.15); 324 + 325 + if (d.photo) { 326 + // Clip path circle (large radius to accommodate scaled nodes) 327 + // We reuse pm-node-clip but with inline style override via foreignObject isn't needed — 328 + // the defs clip is fine for BASE_RADIUS. For connections-scaled nodes, append per-node clip. 329 + const clipId = `pm-clip-${d.id}`; 330 + select(svgEl).select('defs') 331 + .append('clipPath').attr('id', clipId) 332 + .append('circle').attr('r', r); 333 + 334 + g.append('image') 335 + .attr('href', d.photo) 336 + .attr('x', -r).attr('y', -r) 337 + .attr('width', r * 2).attr('height', r * 2) 338 + .attr('clip-path', `url(#${clipId})`) 339 + .attr('preserveAspectRatio', 'xMidYMid slice'); 340 + } else { 341 + g.append('circle') 342 + .attr('r', r) 343 + .attr('fill', color) 344 + .attr('filter', 'url(#pm-node-glow)'); 345 + g.append('text') 346 + .attr('text-anchor', 'middle') 347 + .attr('dominant-baseline', 'central') 348 + .attr('font-size', Math.round(r * 0.5)) 349 + .attr('font-weight', '700') 350 + .attr('font-family', '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif') 351 + .attr('fill', '#ffffff') 352 + .attr('pointer-events', 'none') 353 + .text(initials(d.name)); 354 + } 355 + 356 + // Border ring 357 + g.append('circle') 358 + .attr('class', 'pm-ring') 359 + .attr('r', r) 360 + .attr('fill', 'none') 361 + .attr('stroke', color) 362 + .attr('stroke-width', 2) 363 + .attr('opacity', 0.9); 364 + 365 + // Name label background + text 366 + const labelY = r + LABEL_OFFSET; 367 + g.append('rect') 368 + .attr('class', 'pm-label-bg') 369 + .attr('rx', 4).attr('ry', 4) 370 + .attr('x', -36).attr('y', labelY - 9) 371 + .attr('width', 72).attr('height', 15); 372 + g.append('text') 373 + .attr('class', 'pm-label-text') 374 + .attr('text-anchor', 'middle') 375 + .attr('y', labelY) 376 + .attr('font-size', '11') 377 + .attr('font-family', '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif') 378 + .attr('pointer-events', 'none') 379 + .text(d.name); 380 + }); 381 + 382 + // ── Simulation ──────────────────────────────────────────────────────────── 383 + 384 + const simulation = forceSimulation<NodeDatum>(nodes) 385 + .force( 386 + 'link', 387 + forceLink<NodeDatum, LinkDatum>(links) 388 + .id(d => d.id) 389 + .distance(170) 390 + .strength(0.5) 391 + ) 392 + .force('charge', forceManyBody<NodeDatum>().strength(-500)) 393 + .force('center', forceCenter(480, 360).strength(0.08)) 394 + .force('collision', forceCollide<NodeDatum>(d => nodeRadius(d) + 24)) 395 + .on('tick', ticked); 396 + 397 + function ticked() { 398 + edgeGs.each(function(d) { 399 + const g = select(this); 400 + const x1 = d.source.x, y1 = d.source.y; 401 + const x2 = d.target.x, y2 = d.target.y; 402 + g.select('.pm-edge-bg').attr('x1', x1).attr('y1', y1).attr('x2', x2).attr('y2', y2); 403 + g.select('.pm-edge-line').attr('x1', x1).attr('y1', y1).attr('x2', x2).attr('y2', y2); 404 + g.select('.pm-edge-label').attr('transform', `translate(${(x1 + x2) / 2},${(y1 + y2) / 2})`); 405 + }); 406 + nodeGs.attr('transform', d => `translate(${d.x},${d.y})`); 407 + } 408 + 409 + // ── Drag ───────────────────────────────────────────────────────────────── 410 + 411 + let pinnedNode: NodeDatum | null = null; 412 + 413 + const dragBehavior = d3drag<SVGGElement, NodeDatum>() 414 + .on('start', (event, d) => { 415 + // Unpin any previously pinned node before pinning the new one 416 + if (pinnedNode && pinnedNode !== d) { 417 + pinnedNode.fx = null; 418 + pinnedNode.fy = null; 419 + } 420 + if (!event.active) simulation.alphaTarget(0.3).restart(); 421 + d.fx = d.x; 422 + d.fy = d.y; 423 + pinnedNode = d; 424 + }) 425 + .on('drag', (event, d) => { 426 + d.fx = event.x; 427 + d.fy = event.y; 428 + }) 429 + .on('end', (event) => { 430 + if (!event.active) simulation.alphaTarget(0); 431 + // Node stays pinned until next drag or double-click 432 + }); 433 + 434 + nodeGs.call(dragBehavior); 435 + 436 + // Double-click unpins the node 437 + nodeGs.on('dblclick', (event, d) => { 438 + event.stopPropagation(); 439 + d.fx = null; 440 + d.fy = null; 441 + if (pinnedNode === d) pinnedNode = null; 442 + simulation.alphaTarget(0.1).restart(); 443 + }); 444 + 445 + // ── Zoom ───────────────────────────────────────────────────────────────── 446 + 447 + const zoomBehavior = d3zoom<SVGSVGElement, unknown>() 448 + .scaleExtent([0.05, 8]) 449 + .filter(event => event.type !== 'dblclick') 450 + .on('zoom', event => { 451 + currentTransform = event.transform; 452 + g.attr('transform', event.transform.toString()); 453 + }); 454 + 455 + svg.call(zoomBehavior); 456 + svg.on('dblclick.zoom', null); 457 + 458 + // Click on background dismisses info panel 459 + svg.on('click', () => hideInfoPanel()); 460 + 461 + // Stop click from propagating through SVG background to nodes 462 + nodeGs.on('click', (event, d) => { 463 + event.stopPropagation(); 464 + showInfoPanel(d, event); 465 + }); 466 + 467 + // ── Info panel ──────────────────────────────────────────────────────────── 468 + 469 + const panel = document.createElement('div'); 470 + panel.className = 'pm-info-panel hidden'; 471 + wrap.appendChild(panel); 472 + 473 + function showInfoPanel(d: NodeDatum, event: MouseEvent) { 474 + const color = nodeColor(d.id, d.color); 475 + const c = getThemeColors(isDark); 476 + 477 + panel.style.background = c.panelBg; 478 + panel.style.borderColor = c.panelBorder; 479 + panel.style.color = c.panelText; 480 + 481 + const avatarHtml = d.photo 482 + ? `<img src="${escHtml(d.photo)}" alt="${escHtml(d.name)}"/>` 483 + : `<span style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:${color}">${escHtml(initials(d.name))}</span>`; 484 + 485 + const linksHtml = (d.links ?? []).length > 0 486 + ? `<div class="pm-info-links">${(d.links ?? []).map(l => 487 + `<a class="pm-info-link" href="${escHtml(l.url)}" target="_blank" rel="noopener noreferrer" 488 + style="color:${color};border-color:${color}22">${escHtml(l.label)}</a>` 489 + ).join('')}</div>` 490 + : ''; 491 + 492 + panel.innerHTML = ` 493 + <div class="pm-info-header"> 494 + <div class="pm-info-avatar" style="background:${color}">${avatarHtml}</div> 495 + <div> 496 + <div class="pm-info-name" style="color:${c.panelText}">${escHtml(d.name)}</div> 497 + ${d.pronouns ? `<div class="pm-info-pronouns" style="color:${c.panelMuted}">${escHtml(d.pronouns)}</div>` : ''} 498 + </div> 499 + <button class="pm-info-close" style="color:${c.panelText}" aria-label="Close">×</button> 500 + </div> 501 + ${linksHtml} 502 + `; 503 + 504 + panel.querySelector('.pm-info-close')?.addEventListener('click', hideInfoPanel); 505 + 506 + // Position near the node, clamped to viewport 507 + const wRect = wrap.getBoundingClientRect(); 508 + const nx = currentTransform.applyX(d.x); 509 + const ny = currentTransform.applyY(d.y); 510 + const panelW = 240; 511 + const panelH = 120; 512 + let left = nx + 48; 513 + let top = ny - 40; 514 + if (left + panelW > wRect.width - 8) left = nx - panelW - 48; 515 + if (top + panelH > wRect.height - 8) top = wRect.height - panelH - 8; 516 + if (top < 8) top = 8; 517 + if (left < 8) left = 8; 518 + 519 + panel.style.left = `${left}px`; 520 + panel.style.top = `${top}px`; 521 + panel.classList.remove('hidden'); 522 + } 523 + 524 + function hideInfoPanel() { 525 + panel.classList.add('hidden'); 526 + } 527 + 528 + // ── Controls ───────────────────────────────────────────────────────────── 529 + 530 + const controls = document.createElement('div'); 531 + controls.className = 'pm-controls'; 532 + wrap.appendChild(controls); 533 + 534 + const themeBtn = document.createElement('button'); 535 + themeBtn.className = 'pm-btn'; 536 + themeBtn.title = 'Toggle dark/light mode'; 537 + controls.appendChild(themeBtn); 538 + 539 + const legendBtn = document.createElement('button'); 540 + legendBtn.className = 'pm-btn'; 541 + legendBtn.textContent = 'Legend'; 542 + legendBtn.title = 'Toggle legend'; 543 + controls.appendChild(legendBtn); 544 + 545 + const labelsBtn = document.createElement('button'); 546 + labelsBtn.className = 'pm-btn'; 547 + labelsBtn.title = 'Toggle edge labels'; 548 + controls.appendChild(labelsBtn); 549 + 550 + const namesBtn = document.createElement('button'); 551 + namesBtn.className = 'pm-btn'; 552 + namesBtn.title = 'Toggle node names'; 553 + controls.appendChild(namesBtn); 554 + 555 + const fitBtn = document.createElement('button'); 556 + fitBtn.className = 'pm-btn'; 557 + fitBtn.textContent = '⊡ Fit'; 558 + fitBtn.title = 'Fit graph to view'; 559 + controls.appendChild(fitBtn); 560 + 561 + fitBtn.addEventListener('click', () => { 562 + const svgRect = svgEl.getBoundingClientRect(); 563 + if (!svgRect.width) return; 564 + const xs = nodes.map(n => n.x); 565 + const ys = nodes.map(n => n.y); 566 + const minX = Math.min(...xs) - 80; 567 + const minY = Math.min(...ys) - 80; 568 + const maxX = Math.max(...xs) + 80; 569 + const maxY = Math.max(...ys) + 80; 570 + const w = maxX - minX; 571 + const h = maxY - minY; 572 + const scale = Math.min(0.9, Math.min(svgRect.width / w, svgRect.height / h)); 573 + const tx = svgRect.width / 2 - scale * (minX + w / 2); 574 + const ty = svgRect.height / 2 - scale * (minY + h / 2); 575 + svg.transition().duration(500).call( 576 + zoomBehavior.transform, 577 + zoomIdentity.translate(tx, ty).scale(scale) 578 + ); 579 + }); 580 + 581 + // ── Legend ──────────────────────────────────────────────────────────────── 582 + 583 + const legend = document.createElement('div'); 584 + legend.className = 'pm-legend'; 585 + wrap.appendChild(legend); 586 + 587 + // Build legend from relationship types present in this data 588 + const usedTypes = [...new Set(data.relationships.map(r => r.type))]; 589 + 590 + function buildLegend(c: ReturnType<typeof getThemeColors>) { 591 + legend.style.background = c.legendBg; 592 + legend.style.borderColor = c.panelBorder; 593 + legend.style.color = c.panelText; 594 + 595 + const svgNS = 'http://www.w3.org/2000/svg'; 596 + legend.innerHTML = `<div class="pm-legend-title" style="color:${c.panelMuted}">Relationships</div>` + 597 + usedTypes.map(type => { 598 + const s = RELATIONSHIP_STYLES[type]; 599 + const dash = s.dashArray ? `stroke-dasharray="${s.dashArray}"` : ''; 600 + const lineSvg = `<svg class="pm-legend-line" width="32" height="12" viewBox="0 0 32 12"> 601 + ${s.double 602 + ? `<line x1="0" y1="6" x2="32" y2="6" stroke="${c.panelBg}" stroke-width="${s.width * 2.6}" stroke-linecap="round"/> 603 + <line x1="0" y1="6" x2="32" y2="6" stroke="${s.color}" stroke-width="${s.width}" stroke-linecap="round" ${dash}/>` 604 + : `<line x1="0" y1="6" x2="32" y2="6" stroke="${s.color}" stroke-width="${s.width}" stroke-linecap="round" ${dash}/>` 605 + } 606 + </svg>`; 607 + return `<div class="pm-legend-item" style="color:${c.panelText}">${lineSvg}<span>${s.label}</span></div>`; 608 + }).join(''); 609 + } 610 + 611 + legendBtn.addEventListener('click', () => { 612 + legendVisible = !legendVisible; 613 + legend.classList.toggle('hidden', !legendVisible); 614 + }); 615 + 616 + labelsBtn.addEventListener('click', () => { 617 + labelsVisible = !labelsVisible; 618 + edgeGs.selectAll<SVGGElement, LinkDatum>('.pm-edge-label') 619 + .style('display', labelsVisible ? null : 'none'); 620 + applyTheme(); 621 + }); 622 + 623 + namesBtn.addEventListener('click', () => { 624 + namesVisible = !namesVisible; 625 + nodeGs.selectAll<SVGElement, NodeDatum>('.pm-label-bg, .pm-label-text') 626 + .style('display', namesVisible ? null : 'none'); 627 + applyTheme(); 628 + }); 629 + 630 + // ── Theme application ───────────────────────────────────────────────────── 631 + 632 + function applyTheme() { 633 + const c = getThemeColors(isDark); 634 + 635 + // SVG background 636 + bgRect.attr('fill', c.bg); 637 + svg.selectAll<SVGPathElement, unknown>('.pm-grid-path').attr('stroke', c.grid); 638 + 639 + // Edge label backgrounds 640 + edgeGs.each(function(d) { 641 + const s = RELATIONSHIP_STYLES[d.relationship.type]; 642 + select(this).select<SVGRectElement>('.pm-edge-label rect').attr('fill', c.edgeLabelBg); 643 + if (s.double) { 644 + select(this).select<SVGLineElement>('.pm-edge-bg').attr('stroke', c.bg); 645 + } 646 + }); 647 + 648 + // Node label backgrounds + text 649 + nodeGs.each(function() { 650 + select(this).select<SVGRectElement>('.pm-label-bg').attr('fill', c.nodeLabelBg); 651 + select(this).select<SVGTextElement>('.pm-label-text').attr('fill', c.text); 652 + }); 653 + 654 + // Controls + legend 655 + [themeBtn, legendBtn, labelsBtn, namesBtn, fitBtn].forEach(b => { 656 + b.style.background = c.btnBg; 657 + b.style.borderColor = c.btnBorder; 658 + b.style.color = c.btnText; 659 + }); 660 + themeBtn.textContent = isDark ? '☀ Light' : '☾ Dark'; 661 + labelsBtn.textContent = labelsVisible ? 'Labels On' : 'Labels Off'; 662 + namesBtn.textContent = namesVisible ? 'Names On' : 'Names Off'; 663 + 664 + buildLegend(c); 665 + } 666 + 667 + themeBtn.addEventListener('click', () => { 668 + isDark = !isDark; 669 + applyTheme(); 670 + }); 671 + 672 + // Initial theme pass 673 + applyTheme(); 674 + 675 + // Initial fit after simulation settles 676 + setTimeout(() => fitBtn.click(), 600); 677 + } 678 + 679 + // ─── Escape helper ──────────────────────────────────────────────────────────── 680 + 681 + function escHtml(s: string): string { 682 + return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); 683 + } 684 + 685 + // ─── Auto-init ──────────────────────────────────────────────────────────────── 686 + 687 + if (typeof window !== 'undefined') { 688 + function tryInit() { 689 + const data = (window as Record<string, unknown>)['__POLYMAP_DATA__'] as PolyculeData | undefined; 690 + const container = document.getElementById('polymap-root'); 691 + if (data && container) init(container, data); 692 + } 693 + if (document.readyState === 'loading') { 694 + document.addEventListener('DOMContentLoaded', tryInit); 695 + } else { 696 + tryInit(); 697 + } 698 + }
+48
src/exporters/html.ts
··· 1 + import type { PolyculeData } from '../types.js'; 2 + // Generated by: npm run build:client 3 + // If this import errors, run `npm run build:client` first. 4 + import { CLIENT_BUNDLE } from '../generated/client-bundle.js'; 5 + 6 + export interface HtmlOptions { 7 + title?: string; 8 + } 9 + 10 + export function generateStandaloneHTML(data: PolyculeData, opts: HtmlOptions = {}): string { 11 + const title = opts.title ?? 'Polycule Map'; 12 + const jsonData = JSON.stringify(data); 13 + const dark = data.settings.theme === 'dark'; 14 + 15 + return `<!DOCTYPE html> 16 + <html lang="en"> 17 + <head> 18 + <meta charset="UTF-8"/> 19 + <meta name="viewport" content="width=device-width, initial-scale=1.0"/> 20 + <title>${escHtml(title)}</title> 21 + <style> 22 + *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } 23 + html, body { width: 100%; height: 100%; overflow: hidden; } 24 + body { background: ${dark ? '#0d1117' : '#f0f4f8'}; } 25 + #polymap-root { width: 100vw; height: 100vh; } 26 + </style> 27 + </head> 28 + <body> 29 + <div id="polymap-root"></div> 30 + <script>window.__POLYMAP_DATA__ = ${jsonData};</script> 31 + <script>${CLIENT_BUNDLE}</script> 32 + </body> 33 + </html>`; 34 + } 35 + 36 + /** 37 + * Generates a self-contained embed script. 38 + * Usage: add <div id="polymap-root" style="width:100%;height:600px;"></div> 39 + * then <script src="polycule-embed.js"></script> 40 + */ 41 + export function generateEmbedJS(data: PolyculeData): string { 42 + const jsonData = JSON.stringify(data); 43 + return `(function(){window.__POLYMAP_DATA__=${jsonData};${CLIENT_BUNDLE}})();`; 44 + } 45 + 46 + function escHtml(s: string): string { 47 + return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); 48 + }
+116
src/exporters/png.ts
··· 1 + import { Resvg } from '@resvg/resvg-js'; 2 + import sharp from 'sharp'; 3 + import type { PolyculeData } from '../types.js'; 4 + import type { SimNode, SimLink } from '../simulate.js'; 5 + import { generateSVG } from './svg.js'; 6 + import { NODE_RADIUS } from '../simulate.js'; 7 + 8 + const FETCH_TIMEOUT_MS = 15_000; 9 + const FETCH_RETRIES = 2; 10 + /** Render at 3× the base node diameter for crisp output */ 11 + const CROP_SIZE = NODE_RADIUS * 2 * 3; 12 + 13 + /** 14 + * Fetch an image URL and convert it to a circular-cropped PNG data URI. 15 + * Uses sharp to handle any input format (JPEG, WebP, AVIF, PNG, …) and 16 + * applies a circle alpha mask so no SVG clipPath is needed in resvg. 17 + */ 18 + async function fetchCircularPng(url: string): Promise<string | null> { 19 + let rawBuf: Buffer | null = null; 20 + 21 + for (let attempt = 1; attempt <= FETCH_RETRIES; attempt++) { 22 + const controller = new AbortController(); 23 + const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); 24 + try { 25 + const res = await fetch(url, { signal: controller.signal }); 26 + clearTimeout(timer); 27 + if (!res.ok) { 28 + if (attempt < FETCH_RETRIES) continue; 29 + return null; 30 + } 31 + rawBuf = Buffer.from(await res.arrayBuffer()); 32 + break; 33 + } catch (err) { 34 + clearTimeout(timer); 35 + const reason = err instanceof Error ? err.message : String(err); 36 + if (attempt < FETCH_RETRIES) { 37 + process.stderr.write(` Retry ${attempt}/${FETCH_RETRIES - 1} for ${url} (${reason})\n`); 38 + continue; 39 + } 40 + return null; 41 + } 42 + } 43 + 44 + if (!rawBuf) return null; 45 + 46 + try { 47 + // SVG circle mask — white fill inside circle, transparent outside 48 + const mask = Buffer.from( 49 + `<svg xmlns="http://www.w3.org/2000/svg" width="${CROP_SIZE}" height="${CROP_SIZE}">` + 50 + `<circle cx="${CROP_SIZE / 2}" cy="${CROP_SIZE / 2}" r="${CROP_SIZE / 2}" fill="white"/>` + 51 + `</svg>` 52 + ); 53 + 54 + const png = await sharp(rawBuf) 55 + .resize(CROP_SIZE, CROP_SIZE, { fit: 'cover', position: 'centre' }) 56 + .composite([{ input: mask, blend: 'dest-in' }]) 57 + .png() 58 + .toBuffer(); 59 + 60 + return `data:image/png;base64,${png.toString('base64')}`; 61 + } catch (err) { 62 + process.stderr.write(` Warning: could not process image ${url}: ${err}\n`); 63 + return null; 64 + } 65 + } 66 + 67 + export async function generatePNG( 68 + data: PolyculeData, 69 + nodes: SimNode[], 70 + links: SimLink[], 71 + outputWidth = 1400, 72 + showLegend = false, 73 + showEdgeLabels = true, 74 + showNames = true 75 + ): Promise<Buffer> { 76 + const photoNodes = nodes.filter(n => n.photo); 77 + 78 + if (photoNodes.length > 0) { 79 + process.stdout.write(`Fetching ${photoNodes.length} profile image(s)...`); 80 + } 81 + 82 + const embedImages = new Map<string, string>(); 83 + for (const node of photoNodes) { 84 + const uri = await fetchCircularPng(node.photo!); 85 + if (uri) { 86 + embedImages.set(node.id, uri); 87 + } else { 88 + process.stderr.write( 89 + `\n Warning: could not load photo for "${node.name}" (${node.photo}) — using initials fallback\n` 90 + ); 91 + } 92 + } 93 + 94 + if (photoNodes.length > 0) { 95 + process.stdout.write(` ${embedImages.size}/${photoNodes.length} loaded\n`); 96 + } 97 + 98 + const failedIds = new Set( 99 + photoNodes.filter(n => !embedImages.has(n.id)).map(n => n.id) 100 + ); 101 + 102 + const svgStr = await generateSVG(data, nodes, links, { 103 + embedImages, 104 + failedIds, 105 + preCropped: true, 106 + showLegend, 107 + showEdgeLabels, 108 + showNames, 109 + }); 110 + 111 + const resvg = new Resvg(svgStr, { 112 + fitTo: { mode: 'width', value: outputWidth }, 113 + }); 114 + const rendered = resvg.render(); 115 + return Buffer.from(rendered.asPng()); 116 + }
+225
src/exporters/svg.ts
··· 1 + import type { PolyculeData } from '../types.js'; 2 + import type { SimNode, SimLink } from '../simulate.js'; 3 + import { RELATIONSHIP_STYLES, nodeColor, initials } from '../styles.js'; 4 + 5 + const PADDING = 90; 6 + const LABEL_OFFSET = 16; 7 + 8 + export interface SvgOptions { 9 + /** Replace photo URLs with base64 data URIs for offline/PNG rendering */ 10 + embedImages?: Map<string, string>; 11 + /** Node IDs whose photo fetch failed — render initials instead */ 12 + failedIds?: Set<string>; 13 + /** 14 + * Images in embedImages are pre-cropped circular PNGs (transparent outside circle). 15 + * When true: skip SVG clipPath entirely — just embed image directly. 16 + * Required for resvg, which silently drops unsupported image formats and has 17 + * inconsistent clipPath-on-image support. 18 + */ 19 + preCropped?: boolean; 20 + /** Render a relationship-type legend in the bottom-left corner */ 21 + showLegend?: boolean; 22 + /** Show edge label text (default true) */ 23 + showEdgeLabels?: boolean; 24 + /** Show node name labels (default true) */ 25 + showNames?: boolean; 26 + } 27 + 28 + export async function generateSVG( 29 + data: PolyculeData, 30 + nodes: SimNode[], 31 + links: SimLink[], 32 + opts: SvgOptions = {} 33 + ): Promise<string> { 34 + const dark = data.settings.theme === 'dark'; 35 + const bg = dark ? '#0d1117' : '#f0f4f8'; 36 + const grid = dark ? 'rgba(255,255,255,0.025)' : 'rgba(0,0,0,0.04)'; 37 + const textColor = dark ? '#e6edf3' : '#1c1e21'; 38 + const nodeLabelBg = dark ? 'rgba(0,0,0,0.55)' : 'rgba(255,255,255,0.75)'; 39 + const edgeLabelBg = dark ? 'rgba(13,17,23,0.75)' : 'rgba(240,244,248,0.8)'; 40 + 41 + // Compute viewBox from node positions 42 + const xs = nodes.map(n => n.x); 43 + const ys = nodes.map(n => n.y); 44 + const minX = Math.min(...xs) - PADDING; 45 + const minY = Math.min(...ys) - PADDING; 46 + const maxX = Math.max(...xs) + PADDING; 47 + const maxY = Math.max(...ys) + PADDING; 48 + const vbW = maxX - minX; 49 + const vbH = maxY - minY; 50 + 51 + const lines: string[] = []; 52 + 53 + lines.push(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"`); 54 + lines.push(` viewBox="${minX} ${minY} ${vbW} ${vbH}" width="${vbW}" height="${vbH}">`); 55 + 56 + // ── Defs ────────────────────────────────────────────────────────────────── 57 + 58 + lines.push('<defs>'); 59 + 60 + // Grid pattern 61 + lines.push(` <pattern id="pm-grid" width="40" height="40" patternUnits="userSpaceOnUse">`); 62 + lines.push(` <path d="M 40 0 L 0 0 0 40" fill="none" stroke="${grid}" stroke-width="1"/>`); 63 + lines.push(` </pattern>`); 64 + 65 + // Glow filters 66 + lines.push(` <filter id="pm-node-glow" x="-60%" y="-60%" width="220%" height="220%">`); 67 + lines.push(` <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"/>`); 68 + lines.push(` <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>`); 69 + lines.push(` </filter>`); 70 + lines.push(` <filter id="pm-edge-glow" x="-40%" y="-40%" width="180%" height="180%">`); 71 + lines.push(` <feGaussianBlur in="SourceGraphic" stdDeviation="2.5" result="blur"/>`); 72 + lines.push(` <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>`); 73 + lines.push(` </filter>`); 74 + 75 + // Per-node clip paths for photos — not needed when images are pre-cropped 76 + for (const node of nodes) { 77 + if (!opts.preCropped && node.photo && !opts.failedIds?.has(node.id)) { 78 + const r = BASE_R(data, node); 79 + lines.push(` <clipPath id="pm-clip-${esc(node.id)}">`); 80 + lines.push(` <circle cx="${node.x}" cy="${node.y}" r="${r}"/>`); 81 + lines.push(` </clipPath>`); 82 + } 83 + } 84 + 85 + lines.push('</defs>'); 86 + 87 + // ── Background ──────────────────────────────────────────────────────────── 88 + 89 + lines.push(`<rect x="${minX}" y="${minY}" width="${vbW}" height="${vbH}" fill="${bg}"/>`); 90 + lines.push(`<rect x="${minX}" y="${minY}" width="${vbW}" height="${vbH}" fill="url(#pm-grid)"/>`); 91 + 92 + // ── Edges ───────────────────────────────────────────────────────────────── 93 + 94 + lines.push('<g class="edges">'); 95 + for (const link of links) { 96 + const s = RELATIONSHIP_STYLES[link.relationship.type]; 97 + const x1 = link.source.x, y1 = link.source.y; 98 + const x2 = link.target.x, y2 = link.target.y; 99 + const mx = (x1 + x2) / 2, my = (y1 + y2) / 2; 100 + 101 + if (s.double) { 102 + lines.push(` <line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}"`); 103 + lines.push(` stroke="${bg}" stroke-width="${s.width * 2.6}" stroke-linecap="round"/>`); 104 + } 105 + 106 + const dash = s.dashArray ? ` stroke-dasharray="${s.dashArray}"` : ''; 107 + lines.push(` <line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}"`); 108 + lines.push(` stroke="${s.color}" stroke-width="${s.width}"${dash}`); 109 + lines.push(` stroke-linecap="round" opacity="0.85" filter="url(#pm-edge-glow)"/>`); 110 + 111 + if (opts.showEdgeLabels !== false) { 112 + const labelText = link.relationship.label ?? s.label; 113 + const lw = Math.max(40, labelText.length * 5.5 + 12); 114 + lines.push(` <rect x="${mx - lw / 2}" y="${my - 7}" width="${lw}" height="13" rx="4" fill="${edgeLabelBg}"/>`); 115 + lines.push(` <text x="${mx}" y="${my + 3}" text-anchor="middle" font-size="9"`); 116 + lines.push(` font-family="system-ui, sans-serif" font-weight="500" fill="${s.color}">`); 117 + lines.push(` ${esc(labelText)}`); 118 + lines.push(` </text>`); 119 + } 120 + } 121 + lines.push('</g>'); 122 + 123 + // ── Nodes ───────────────────────────────────────────────────────────────── 124 + 125 + lines.push('<g class="nodes">'); 126 + for (const node of nodes) { 127 + const r = BASE_R(data, node); 128 + const color = nodeColor(node.id, node.color); 129 + const inits = initials(node.name); 130 + const labelY = node.y + r + LABEL_OFFSET; 131 + 132 + // Halo 133 + lines.push(` <circle cx="${node.x}" cy="${node.y}" r="${r + 10}" fill="${color}" opacity="0.15"/>`); 134 + 135 + // Use photo if available and not in the failed-fetch set 136 + const usePhoto = node.photo && !opts.failedIds?.has(node.id); 137 + if (usePhoto) { 138 + const href = opts.embedImages?.get(node.id) ?? node.photo!; 139 + if (opts.preCropped) { 140 + // Image already cropped to circle with transparent bg — embed directly, no clipPath 141 + lines.push(` <image href="${esc(href)}" x="${node.x - r}" y="${node.y - r}"`); 142 + lines.push(` width="${r * 2}" height="${r * 2}"/>`); 143 + } else { 144 + // Browser/SVG export: use clipPath for circular crop 145 + lines.push(` <image href="${esc(href)}" x="${node.x - r}" y="${node.y - r}"`); 146 + lines.push(` width="${r * 2}" height="${r * 2}"`); 147 + lines.push(` clip-path="url(#pm-clip-${esc(node.id)})" preserveAspectRatio="xMidYMid slice"/>`); 148 + } 149 + lines.push(` <circle cx="${node.x}" cy="${node.y}" r="${r}" fill="none"`); 150 + lines.push(` stroke="${color}" stroke-width="2" opacity="0.9"/>`); 151 + } else { 152 + lines.push(` <circle cx="${node.x}" cy="${node.y}" r="${r}" fill="${color}" filter="url(#pm-node-glow)"/>`); 153 + lines.push(` <text x="${node.x}" y="${node.y}" text-anchor="middle" dominant-baseline="central"`); 154 + lines.push(` font-size="${Math.round(r * 0.5)}" font-weight="700"`); 155 + lines.push(` font-family="system-ui, sans-serif" fill="white">${esc(inits)}</text>`); 156 + } 157 + 158 + if (opts.showNames !== false) { 159 + const nw = Math.max(40, node.name.length * 6.5 + 12); 160 + lines.push(` <rect x="${node.x - nw / 2}" y="${labelY - 9}" width="${nw}" height="15" rx="4" fill="${nodeLabelBg}"/>`); 161 + lines.push(` <text x="${node.x}" y="${labelY}" text-anchor="middle" font-size="11"`); 162 + lines.push(` font-family="system-ui, sans-serif" fill="${textColor}">${esc(node.name)}</text>`); 163 + } 164 + } 165 + lines.push('</g>'); 166 + 167 + // ── Legend ─────────────────────────────────────────────────────────────── 168 + 169 + if (opts.showLegend) { 170 + const usedTypes = [...new Set(data.relationships.map(r => r.type))]; 171 + const ROW_H = 20; 172 + const PAD = 12; 173 + const LINE_W = 32; 174 + const TEXT_X = LINE_W + 10; 175 + const legendW = 185; 176 + const titleH = 22; 177 + const legendH = titleH + usedTypes.length * ROW_H + PAD; 178 + const legendX = minX + PAD; 179 + const legendY = minY + vbH - legendH - PAD; 180 + const legendBg = dark ? 'rgba(13,17,23,0.45)' : 'rgba(255,255,255,0.5)'; 181 + const titleColor = dark ? '#8b949e' : '#65676b'; 182 + 183 + lines.push(`<g class="pm-legend" transform="translate(${legendX},${legendY})">`); 184 + lines.push(` <rect width="${legendW}" height="${legendH}" rx="8" fill="${legendBg}"/>`); 185 + lines.push(` <text x="${PAD}" y="${PAD + 8}" font-size="10" font-weight="600"`); 186 + lines.push(` font-family="system-ui, sans-serif" fill="${titleColor}"`); 187 + lines.push(` letter-spacing="0.05em">RELATIONSHIPS</text>`); 188 + 189 + usedTypes.forEach((type, i) => { 190 + const s = RELATIONSHIP_STYLES[type]; 191 + const rowY = titleH + i * ROW_H; 192 + const lineY = rowY + ROW_H / 2; 193 + const dash = s.dashArray ? ` stroke-dasharray="${s.dashArray}"` : ''; 194 + 195 + lines.push(` <g transform="translate(${PAD},0)">`); 196 + if (s.double) { 197 + lines.push(` <line x1="0" y1="${lineY}" x2="${LINE_W}" y2="${lineY}" stroke="${legendBg}" stroke-width="${s.width * 2.6}" stroke-linecap="round"/>`); 198 + } 199 + lines.push(` <line x1="0" y1="${lineY}" x2="${LINE_W}" y2="${lineY}" stroke="${s.color}" stroke-width="${s.width}"${dash} stroke-linecap="round"/>`); 200 + lines.push(` <text x="${TEXT_X}" y="${lineY + 4}" font-size="11"`); 201 + lines.push(` font-family="system-ui, sans-serif" fill="${textColor}">${esc(s.label)}</text>`); 202 + lines.push(` </g>`); 203 + }); 204 + 205 + lines.push('</g>'); 206 + } 207 + 208 + lines.push('</svg>'); 209 + return lines.join('\n'); 210 + } 211 + 212 + function BASE_R(data: PolyculeData, node: SimNode): number { 213 + if (data.settings.nodeScale === 'connections') { 214 + return 28 + node.connectionCount * 4; 215 + } 216 + return 28; 217 + } 218 + 219 + function esc(s: string): string { 220 + return s 221 + .replace(/&/g, '&amp;') 222 + .replace(/</g, '&lt;') 223 + .replace(/>/g, '&gt;') 224 + .replace(/"/g, '&quot;'); 225 + }
+3
src/generated/client-bundle.ts
··· 1 + // AUTO-GENERATED — do not edit manually. 2 + // Regenerate with: npm run build:client 3 + export const CLIENT_BUNDLE = "\"use strict\";var _PM=(()=>{var ue=Object.defineProperty,Li=Object.defineProperties,Pi=Object.getOwnPropertyDescriptor,Gi=Object.getOwnPropertyDescriptors,Vi=Object.getOwnPropertyNames,yn=Object.getOwnPropertySymbols;var wn=Object.prototype.hasOwnProperty,Fi=Object.prototype.propertyIsEnumerable;var vn=(t,e,n)=>e in t?ue(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,_n=(t,e)=>{for(var n in e||(e={}))wn.call(e,n)&&vn(t,n,e[n]);if(yn)for(var n of yn(e))Fi.call(e,n)&&vn(t,n,e[n]);return t},bn=(t,e)=>Li(t,Gi(e));var Hi=(t,e)=>{for(var n in e)ue(t,n,{get:e[n],enumerable:!0})},Xi=(t,e,n,r)=>{if(e&&typeof e==\"object\"||typeof e==\"function\")for(let i of Vi(e))!wn.call(t,i)&&i!==n&&ue(t,i,{get:()=>e[i],enumerable:!(r=Pi(e,i))||r.enumerable});return t};var Yi=t=>Xi(ue({},\"__esModule\",{value:!0}),t);var gs={};Hi(gs,{init:()=>Di});function Le(t,e){var n,r=1;t==null&&(t=0),e==null&&(e=0);function i(){var o,a=n.length,s,u=0,l=0;for(o=0;o<a;++o)s=n[o],u+=s.x,l+=s.y;for(u=(u/a-t)*r,l=(l/a-e)*r,o=0;o<a;++o)s=n[o],s.x-=u,s.y-=l}return i.initialize=function(o){n=o},i.x=function(o){return arguments.length?(t=+o,i):t},i.y=function(o){return arguments.length?(e=+o,i):e},i.strength=function(o){return arguments.length?(r=+o,i):r},i}function Nn(t){let e=+this._x.call(null,t),n=+this._y.call(null,t);return kn(this.cover(e,n),e,n,t)}function kn(t,e,n,r){if(isNaN(e)||isNaN(n))return t;var i,o=t._root,a={data:r},s=t._x0,u=t._y0,l=t._x1,f=t._y1,x,p,h,_,g,y,d,b;if(!o)return t._root=a,t;for(;o.length;)if((g=e>=(x=(s+l)/2))?s=x:l=x,(y=n>=(p=(u+f)/2))?u=p:f=p,i=o,!(o=o[d=y<<1|g]))return i[d]=a,t;if(h=+t._x.call(null,o.data),_=+t._y.call(null,o.data),e===h&&n===_)return a.next=o,i?i[d]=a:t._root=a,t;do i=i?i[d]=new Array(4):t._root=new Array(4),(g=e>=(x=(s+l)/2))?s=x:l=x,(y=n>=(p=(u+f)/2))?u=p:f=p;while((d=y<<1|g)===(b=(_>=p)<<1|h>=x));return i[b]=o,i[d]=a,t}function En(t){var e,n,r=t.length,i,o,a=new Array(r),s=new Array(r),u=1/0,l=1/0,f=-1/0,x=-1/0;for(n=0;n<r;++n)isNaN(i=+this._x.call(null,e=t[n]))||isNaN(o=+this._y.call(null,e))||(a[n]=i,s[n]=o,i<u&&(u=i),i>f&&(f=i),o<l&&(l=o),o>x&&(x=o));if(u>f||l>x)return this;for(this.cover(u,l).cover(f,x),n=0;n<r;++n)kn(this,a[n],s[n],t[n]);return this}function An(t,e){if(isNaN(t=+t)||isNaN(e=+e))return this;var n=this._x0,r=this._y0,i=this._x1,o=this._y1;if(isNaN(n))i=(n=Math.floor(t))+1,o=(r=Math.floor(e))+1;else{for(var a=i-n||1,s=this._root,u,l;n>t||t>=i||r>e||e>=o;)switch(l=(e<r)<<1|t<n,u=new Array(4),u[l]=s,s=u,a*=2,l){case 0:i=n+a,o=r+a;break;case 1:n=i-a,o=r+a;break;case 2:i=n+a,r=o-a;break;case 3:n=i-a,r=o-a;break}this._root&&this._root.length&&(this._root=s)}return this._x0=n,this._y0=r,this._x1=i,this._y1=o,this}function Mn(){var t=[];return this.visit(function(e){if(!e.length)do t.push(e.data);while(e=e.next)}),t}function Sn(t){return arguments.length?this.cover(+t[0][0],+t[0][1]).cover(+t[1][0],+t[1][1]):isNaN(this._x0)?void 0:[[this._x0,this._y0],[this._x1,this._y1]]}function K(t,e,n,r,i){this.node=t,this.x0=e,this.y0=n,this.x1=r,this.y1=i}function Tn(t,e,n){var r,i=this._x0,o=this._y0,a,s,u,l,f=this._x1,x=this._y1,p=[],h=this._root,_,g;for(h&&p.push(new K(h,i,o,f,x)),n==null?n=1/0:(i=t-n,o=e-n,f=t+n,x=e+n,n*=n);_=p.pop();)if(!(!(h=_.node)||(a=_.x0)>f||(s=_.y0)>x||(u=_.x1)<i||(l=_.y1)<o))if(h.length){var y=(a+u)/2,d=(s+l)/2;p.push(new K(h[3],y,d,u,l),new K(h[2],a,d,y,l),new K(h[1],y,s,u,d),new K(h[0],a,s,y,d)),(g=(e>=d)<<1|t>=y)&&(_=p[p.length-1],p[p.length-1]=p[p.length-1-g],p[p.length-1-g]=_)}else{var b=t-+this._x.call(null,h.data),A=e-+this._y.call(null,h.data),m=b*b+A*A;if(m<n){var E=Math.sqrt(n=m);i=t-E,o=e-E,f=t+E,x=e+E,r=h.data}}return r}function Cn(t){if(isNaN(f=+this._x.call(null,t))||isNaN(x=+this._y.call(null,t)))return this;var e,n=this._root,r,i,o,a=this._x0,s=this._y0,u=this._x1,l=this._y1,f,x,p,h,_,g,y,d;if(!n)return this;if(n.length)for(;;){if((_=f>=(p=(a+u)/2))?a=p:u=p,(g=x>=(h=(s+l)/2))?s=h:l=h,e=n,!(n=n[y=g<<1|_]))return this;if(!n.length)break;(e[y+1&3]||e[y+2&3]||e[y+3&3])&&(r=e,d=y)}for(;n.data!==t;)if(i=n,!(n=n.next))return this;return(o=n.next)&&delete n.next,i?(o?i.next=o:delete i.next,this):e?(o?e[y]=o:delete e[y],(n=e[0]||e[1]||e[2]||e[3])&&n===(e[3]||e[2]||e[1]||e[0])&&!n.length&&(r?r[d]=n:this._root=n),this):(this._root=o,this)}function In(t){for(var e=0,n=t.length;e<n;++e)this.remove(t[e]);return this}function zn(){return this._root}function $n(){var t=0;return this.visit(function(e){if(!e.length)do++t;while(e=e.next)}),t}function On(t){var e=[],n,r=this._root,i,o,a,s,u;for(r&&e.push(new K(r,this._x0,this._y0,this._x1,this._y1));n=e.pop();)if(!t(r=n.node,o=n.x0,a=n.y0,s=n.x1,u=n.y1)&&r.length){var l=(o+s)/2,f=(a+u)/2;(i=r[3])&&e.push(new K(i,l,f,s,u)),(i=r[2])&&e.push(new K(i,o,f,l,u)),(i=r[1])&&e.push(new K(i,l,a,s,f)),(i=r[0])&&e.push(new K(i,o,a,l,f))}return this}function Dn(t){var e=[],n=[],r;for(this._root&&e.push(new K(this._root,this._x0,this._y0,this._x1,this._y1));r=e.pop();){var i=r.node;if(i.length){var o,a=r.x0,s=r.y0,u=r.x1,l=r.y1,f=(a+u)/2,x=(s+l)/2;(o=i[0])&&e.push(new K(o,a,s,f,x)),(o=i[1])&&e.push(new K(o,f,s,u,x)),(o=i[2])&&e.push(new K(o,a,x,f,l)),(o=i[3])&&e.push(new K(o,f,x,u,l))}n.push(r)}for(;r=n.pop();)t(r.node,r.x0,r.y0,r.x1,r.y1);return this}function Bn(t){return t[0]}function Rn(t){return arguments.length?(this._x=t,this):this._x}function Ln(t){return t[1]}function Pn(t){return arguments.length?(this._y=t,this):this._y}function At(t,e,n){var r=new Pe(e==null?Bn:e,n==null?Ln:n,NaN,NaN,NaN,NaN);return t==null?r:r.addAll(t)}function Pe(t,e,n,r,i,o){this._x=t,this._y=e,this._x0=n,this._y0=r,this._x1=i,this._y1=o,this._root=void 0}function Gn(t){for(var e={data:t.data},n=e;t=t.next;)n=n.next={data:t.data};return e}var et=At.prototype=Pe.prototype;et.copy=function(){var t=new Pe(this._x,this._y,this._x0,this._y0,this._x1,this._y1),e=this._root,n,r;if(!e)return t;if(!e.length)return t._root=Gn(e),t;for(n=[{source:e,target:t._root=new Array(4)}];e=n.pop();)for(var i=0;i<4;++i)(r=e.source[i])&&(r.length?n.push({source:r,target:e.target[i]=new Array(4)}):e.target[i]=Gn(r));return t};et.add=Nn;et.addAll=En;et.cover=An;et.data=Mn;et.extent=Sn;et.find=Tn;et.remove=Cn;et.removeAll=In;et.root=zn;et.size=$n;et.visit=On;et.visitAfter=Dn;et.x=Rn;et.y=Pn;function ct(t){return function(){return t}}function lt(t){return(t()-.5)*1e-6}function qi(t){return t.x+t.vx}function Ui(t){return t.y+t.vy}function Ge(t){var e,n,r,i=1,o=1;typeof t!=\"function\"&&(t=ct(t==null?1:+t));function a(){for(var l,f=e.length,x,p,h,_,g,y,d=0;d<o;++d)for(x=At(e,qi,Ui).visitAfter(s),l=0;l<f;++l)p=e[l],g=n[p.index],y=g*g,h=p.x+p.vx,_=p.y+p.vy,x.visit(b);function b(A,m,E,T,I){var $=A.data,L=A.r,O=g+L;if($){if($.index>p.index){var F=h-$.x-$.vx,rt=_-$.y-$.vy,Z=F*F+rt*rt;Z<O*O&&(F===0&&(F=lt(r),Z+=F*F),rt===0&&(rt=lt(r),Z+=rt*rt),Z=(O-(Z=Math.sqrt(Z)))/Z*i,p.vx+=(F*=Z)*(O=(L*=L)/(y+L)),p.vy+=(rt*=Z)*O,$.vx-=F*(O=1-O),$.vy-=rt*O)}return}return m>h+O||T<h-O||E>_+O||I<_-O}}function s(l){if(l.data)return l.r=n[l.data.index];for(var f=l.r=0;f<4;++f)l[f]&&l[f].r>l.r&&(l.r=l[f].r)}function u(){if(e){var l,f=e.length,x;for(n=new Array(f),l=0;l<f;++l)x=e[l],n[x.index]=+t(x,l,e)}}return a.initialize=function(l,f){e=l,r=f,u()},a.iterations=function(l){return arguments.length?(o=+l,a):o},a.strength=function(l){return arguments.length?(i=+l,a):i},a.radius=function(l){return arguments.length?(t=typeof l==\"function\"?l:ct(+l),u(),a):t},a}function Qi(t){return t.index}function Vn(t,e){var n=t.get(e);if(!n)throw new Error(\"node not found: \"+e);return n}function Ve(t){var e=Qi,n=x,r,i=ct(30),o,a,s,u,l,f=1;t==null&&(t=[]);function x(y){return 1/Math.min(s[y.source.index],s[y.target.index])}function p(y){for(var d=0,b=t.length;d<f;++d)for(var A=0,m,E,T,I,$,L,O;A<b;++A)m=t[A],E=m.source,T=m.target,I=T.x+T.vx-E.x-E.vx||lt(l),$=T.y+T.vy-E.y-E.vy||lt(l),L=Math.sqrt(I*I+$*$),L=(L-o[A])/L*y*r[A],I*=L,$*=L,T.vx-=I*(O=u[A]),T.vy-=$*O,E.vx+=I*(O=1-O),E.vy+=$*O}function h(){if(a){var y,d=a.length,b=t.length,A=new Map(a.map((E,T)=>[e(E,T,a),E])),m;for(y=0,s=new Array(d);y<b;++y)m=t[y],m.index=y,typeof m.source!=\"object\"&&(m.source=Vn(A,m.source)),typeof m.target!=\"object\"&&(m.target=Vn(A,m.target)),s[m.source.index]=(s[m.source.index]||0)+1,s[m.target.index]=(s[m.target.index]||0)+1;for(y=0,u=new Array(b);y<b;++y)m=t[y],u[y]=s[m.source.index]/(s[m.source.index]+s[m.target.index]);r=new Array(b),_(),o=new Array(b),g()}}function _(){if(a)for(var y=0,d=t.length;y<d;++y)r[y]=+n(t[y],y,t)}function g(){if(a)for(var y=0,d=t.length;y<d;++y)o[y]=+i(t[y],y,t)}return p.initialize=function(y,d){a=y,l=d,h()},p.links=function(y){return arguments.length?(t=y,h(),p):t},p.id=function(y){return arguments.length?(e=y,p):e},p.iterations=function(y){return arguments.length?(f=+y,p):f},p.strength=function(y){return arguments.length?(n=typeof y==\"function\"?y:ct(+y),_(),p):n},p.distance=function(y){return arguments.length?(i=typeof y==\"function\"?y:ct(+y),g(),p):i},p}var Ki={value:()=>{}};function Hn(){for(var t=0,e=arguments.length,n={},r;t<e;++t){if(!(r=arguments[t]+\"\")||r in n||/[\\s.]/.test(r))throw new Error(\"illegal type: \"+r);n[r]=[]}return new fe(n)}function fe(t){this._=t}function Zi(t,e){return t.trim().split(/^|\\s+/).map(function(n){var r=\"\",i=n.indexOf(\".\");if(i>=0&&(r=n.slice(i+1),n=n.slice(0,i)),n&&!e.hasOwnProperty(n))throw new Error(\"unknown type: \"+n);return{type:n,name:r}})}fe.prototype=Hn.prototype={constructor:fe,on:function(t,e){var n=this._,r=Zi(t+\"\",n),i,o=-1,a=r.length;if(arguments.length<2){for(;++o<a;)if((i=(t=r[o]).type)&&(i=Wi(n[i],t.name)))return i;return}if(e!=null&&typeof e!=\"function\")throw new Error(\"invalid callback: \"+e);for(;++o<a;)if(i=(t=r[o]).type)n[i]=Fn(n[i],t.name,e);else if(e==null)for(i in n)n[i]=Fn(n[i],t.name,null);return this},copy:function(){var t={},e=this._;for(var n in e)t[n]=e[n].slice();return new fe(t)},call:function(t,e){if((i=arguments.length-2)>0)for(var n=new Array(i),r=0,i,o;r<i;++r)n[r]=arguments[r+2];if(!this._.hasOwnProperty(t))throw new Error(\"unknown type: \"+t);for(o=this._[t],r=0,i=o.length;r<i;++r)o[r].value.apply(e,n)},apply:function(t,e,n){if(!this._.hasOwnProperty(t))throw new Error(\"unknown type: \"+t);for(var r=this._[t],i=0,o=r.length;i<o;++i)r[i].value.apply(e,n)}};function Wi(t,e){for(var n=0,r=t.length,i;n<r;++n)if((i=t[n]).name===e)return i.value}function Fn(t,e,n){for(var r=0,i=t.length;r<i;++r)if(t[r].name===e){t[r]=Ki,t=t.slice(0,r).concat(t.slice(r+1));break}return n!=null&&t.push({name:e,value:n}),t}var dt=Hn;var Bt=0,Ht=0,Ft=0,Yn=1e3,ce,Xt,pe=0,Mt=0,he=0,Yt=typeof performance==\"object\"&&performance.now?performance:Date,qn=typeof window==\"object\"&&window.requestAnimationFrame?window.requestAnimationFrame.bind(window):function(t){setTimeout(t,17)};function Ut(){return Mt||(qn(Ji),Mt=Yt.now()+he)}function Ji(){Mt=0}function qt(){this._call=this._time=this._next=null}qt.prototype=Rt.prototype={constructor:qt,restart:function(t,e,n){if(typeof t!=\"function\")throw new TypeError(\"callback is not a function\");n=(n==null?Ut():+n)+(e==null?0:+e),!this._next&&Xt!==this&&(Xt?Xt._next=this:ce=this,Xt=this),this._call=t,this._time=n,Fe()},stop:function(){this._call&&(this._call=null,this._time=1/0,Fe())}};function Rt(t,e,n){var r=new qt;return r.restart(t,e,n),r}function Un(){Ut(),++Bt;for(var t=ce,e;t;)(e=Mt-t._time)>=0&&t._call.call(void 0,e),t=t._next;--Bt}function Xn(){Mt=(pe=Yt.now())+he,Bt=Ht=0;try{Un()}finally{Bt=0,to(),Mt=0}}function ji(){var t=Yt.now(),e=t-pe;e>Yn&&(he-=e,pe=t)}function to(){for(var t,e=ce,n,r=1/0;e;)e._call?(r>e._time&&(r=e._time),t=e,e=e._next):(n=e._next,e._next=null,e=t?t._next=n:ce=n);Xt=t,Fe(r)}function Fe(t){if(!Bt){Ht&&(Ht=clearTimeout(Ht));var e=t-Mt;e>24?(t<1/0&&(Ht=setTimeout(Xn,t-Yt.now()-he)),Ft&&(Ft=clearInterval(Ft))):(Ft||(pe=Yt.now(),Ft=setInterval(ji,Yn)),Bt=1,qn(Xn))}}function me(t,e,n){var r=new qt;return e=e==null?0:+e,r.restart(i=>{r.stop(),t(i+e)},e,n),r}function Qn(){let t=1;return()=>(t=(1664525*t+1013904223)%4294967296)/4294967296}function Kn(t){return t.x}function Zn(t){return t.y}var eo=10,no=Math.PI*(3-Math.sqrt(5));function He(t){var e,n=1,r=.001,i=1-Math.pow(r,1/300),o=0,a=.6,s=new Map,u=Rt(x),l=dt(\"tick\",\"end\"),f=Qn();t==null&&(t=[]);function x(){p(),l.call(\"tick\",e),n<r&&(u.stop(),l.call(\"end\",e))}function p(g){var y,d=t.length,b;g===void 0&&(g=1);for(var A=0;A<g;++A)for(n+=(o-n)*i,s.forEach(function(m){m(n)}),y=0;y<d;++y)b=t[y],b.fx==null?b.x+=b.vx*=a:(b.x=b.fx,b.vx=0),b.fy==null?b.y+=b.vy*=a:(b.y=b.fy,b.vy=0);return e}function h(){for(var g=0,y=t.length,d;g<y;++g){if(d=t[g],d.index=g,d.fx!=null&&(d.x=d.fx),d.fy!=null&&(d.y=d.fy),isNaN(d.x)||isNaN(d.y)){var b=eo*Math.sqrt(.5+g),A=g*no;d.x=b*Math.cos(A),d.y=b*Math.sin(A)}(isNaN(d.vx)||isNaN(d.vy))&&(d.vx=d.vy=0)}}function _(g){return g.initialize&&g.initialize(t,f),g}return h(),e={tick:p,restart:function(){return u.restart(x),e},stop:function(){return u.stop(),e},nodes:function(g){return arguments.length?(t=g,h(),s.forEach(_),e):t},alpha:function(g){return arguments.length?(n=+g,e):n},alphaMin:function(g){return arguments.length?(r=+g,e):r},alphaDecay:function(g){return arguments.length?(i=+g,e):+i},alphaTarget:function(g){return arguments.length?(o=+g,e):o},velocityDecay:function(g){return arguments.length?(a=1-g,e):1-a},randomSource:function(g){return arguments.length?(f=g,s.forEach(_),e):f},force:function(g,y){return arguments.length>1?(y==null?s.delete(g):s.set(g,_(y)),e):s.get(g)},find:function(g,y,d){var b=0,A=t.length,m,E,T,I,$;for(d==null?d=1/0:d*=d,b=0;b<A;++b)I=t[b],m=g-I.x,E=y-I.y,T=m*m+E*E,T<d&&($=I,d=T);return $},on:function(g,y){return arguments.length>1?(l.on(g,y),e):l.on(g)}}}function Xe(){var t,e,n,r,i=ct(-30),o,a=1,s=1/0,u=.81;function l(h){var _,g=t.length,y=At(t,Kn,Zn).visitAfter(x);for(r=h,_=0;_<g;++_)e=t[_],y.visit(p)}function f(){if(t){var h,_=t.length,g;for(o=new Array(_),h=0;h<_;++h)g=t[h],o[g.index]=+i(g,h,t)}}function x(h){var _=0,g,y,d=0,b,A,m;if(h.length){for(b=A=m=0;m<4;++m)(g=h[m])&&(y=Math.abs(g.value))&&(_+=g.value,d+=y,b+=y*g.x,A+=y*g.y);h.x=b/d,h.y=A/d}else{g=h,g.x=g.data.x,g.y=g.data.y;do _+=o[g.data.index];while(g=g.next)}h.value=_}function p(h,_,g,y){if(!h.value)return!0;var d=h.x-e.x,b=h.y-e.y,A=y-_,m=d*d+b*b;if(A*A/u<m)return m<s&&(d===0&&(d=lt(n),m+=d*d),b===0&&(b=lt(n),m+=b*b),m<a&&(m=Math.sqrt(a*m)),e.vx+=d*h.value*r/m,e.vy+=b*h.value*r/m),!0;if(h.length||m>=s)return;(h.data!==e||h.next)&&(d===0&&(d=lt(n),m+=d*d),b===0&&(b=lt(n),m+=b*b),m<a&&(m=Math.sqrt(a*m)));do h.data!==e&&(A=o[h.data.index]*r/m,e.vx+=d*A,e.vy+=b*A);while(h=h.next)}return l.initialize=function(h,_){t=h,n=_,f()},l.strength=function(h){return arguments.length?(i=typeof h==\"function\"?h:ct(+h),f(),l):i},l.distanceMin=function(h){return arguments.length?(a=h*h,l):Math.sqrt(a)},l.distanceMax=function(h){return arguments.length?(s=h*h,l):Math.sqrt(s)},l.theta=function(h){return arguments.length?(u=h*h,l):Math.sqrt(u)},l}var de=\"http://www.w3.org/1999/xhtml\",Ye={svg:\"http://www.w3.org/2000/svg\",xhtml:de,xlink:\"http://www.w3.org/1999/xlink\",xml:\"http://www.w3.org/XML/1998/namespace\",xmlns:\"http://www.w3.org/2000/xmlns/\"};function gt(t){var e=t+=\"\",n=e.indexOf(\":\");return n>=0&&(e=t.slice(0,n))!==\"xmlns\"&&(t=t.slice(n+1)),Ye.hasOwnProperty(e)?{space:Ye[e],local:t}:t}function ro(t){return function(){var e=this.ownerDocument,n=this.namespaceURI;return n===de&&e.documentElement.namespaceURI===de?e.createElement(t):e.createElementNS(n,t)}}function io(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function ge(t){var e=gt(t);return(e.local?io:ro)(e)}function oo(){}function St(t){return t==null?oo:function(){return this.querySelector(t)}}function Wn(t){typeof t!=\"function\"&&(t=St(t));for(var e=this._groups,n=e.length,r=new Array(n),i=0;i<n;++i)for(var o=e[i],a=o.length,s=r[i]=new Array(a),u,l,f=0;f<a;++f)(u=o[f])&&(l=t.call(u,u.__data__,f,o))&&(\"__data__\"in u&&(l.__data__=u.__data__),s[f]=l);return new H(r,this._parents)}function qe(t){return t==null?[]:Array.isArray(t)?t:Array.from(t)}function ao(){return[]}function Qt(t){return t==null?ao:function(){return this.querySelectorAll(t)}}function so(t){return function(){return qe(t.apply(this,arguments))}}function Jn(t){typeof t==\"function\"?t=so(t):t=Qt(t);for(var e=this._groups,n=e.length,r=[],i=[],o=0;o<n;++o)for(var a=e[o],s=a.length,u,l=0;l<s;++l)(u=a[l])&&(r.push(t.call(u,u.__data__,l,a)),i.push(u));return new H(r,i)}function Kt(t){return function(){return this.matches(t)}}function xe(t){return function(e){return e.matches(t)}}var lo=Array.prototype.find;function uo(t){return function(){return lo.call(this.children,t)}}function fo(){return this.firstElementChild}function jn(t){return this.select(t==null?fo:uo(typeof t==\"function\"?t:xe(t)))}var co=Array.prototype.filter;function po(){return Array.from(this.children)}function ho(t){return function(){return co.call(this.children,t)}}function tr(t){return this.selectAll(t==null?po:ho(typeof t==\"function\"?t:xe(t)))}function er(t){typeof t!=\"function\"&&(t=Kt(t));for(var e=this._groups,n=e.length,r=new Array(n),i=0;i<n;++i)for(var o=e[i],a=o.length,s=r[i]=[],u,l=0;l<a;++l)(u=o[l])&&t.call(u,u.__data__,l,o)&&s.push(u);return new H(r,this._parents)}function ye(t){return new Array(t.length)}function nr(){return new H(this._enter||this._groups.map(ye),this._parents)}function Zt(t,e){this.ownerDocument=t.ownerDocument,this.namespaceURI=t.namespaceURI,this._next=null,this._parent=t,this.__data__=e}Zt.prototype={constructor:Zt,appendChild:function(t){return this._parent.insertBefore(t,this._next)},insertBefore:function(t,e){return this._parent.insertBefore(t,e)},querySelector:function(t){return this._parent.querySelector(t)},querySelectorAll:function(t){return this._parent.querySelectorAll(t)}};function rr(t){return function(){return t}}function mo(t,e,n,r,i,o){for(var a=0,s,u=e.length,l=o.length;a<l;++a)(s=e[a])?(s.__data__=o[a],r[a]=s):n[a]=new Zt(t,o[a]);for(;a<u;++a)(s=e[a])&&(i[a]=s)}function go(t,e,n,r,i,o,a){var s,u,l=new Map,f=e.length,x=o.length,p=new Array(f),h;for(s=0;s<f;++s)(u=e[s])&&(p[s]=h=a.call(u,u.__data__,s,e)+\"\",l.has(h)?i[s]=u:l.set(h,u));for(s=0;s<x;++s)h=a.call(t,o[s],s,o)+\"\",(u=l.get(h))?(r[s]=u,u.__data__=o[s],l.delete(h)):n[s]=new Zt(t,o[s]);for(s=0;s<f;++s)(u=e[s])&&l.get(p[s])===u&&(i[s]=u)}function xo(t){return t.__data__}function ir(t,e){if(!arguments.length)return Array.from(this,xo);var n=e?go:mo,r=this._parents,i=this._groups;typeof t!=\"function\"&&(t=rr(t));for(var o=i.length,a=new Array(o),s=new Array(o),u=new Array(o),l=0;l<o;++l){var f=r[l],x=i[l],p=x.length,h=yo(t.call(f,f&&f.__data__,l,r)),_=h.length,g=s[l]=new Array(_),y=a[l]=new Array(_),d=u[l]=new Array(p);n(f,x,g,y,d,h,e);for(var b=0,A=0,m,E;b<_;++b)if(m=g[b]){for(b>=A&&(A=b+1);!(E=y[A])&&++A<_;);m._next=E||null}}return a=new H(a,r),a._enter=s,a._exit=u,a}function yo(t){return typeof t==\"object\"&&\"length\"in t?t:Array.from(t)}function or(){return new H(this._exit||this._groups.map(ye),this._parents)}function ar(t,e,n){var r=this.enter(),i=this,o=this.exit();return typeof t==\"function\"?(r=t(r),r&&(r=r.selection())):r=r.append(t+\"\"),e!=null&&(i=e(i),i&&(i=i.selection())),n==null?o.remove():n(o),r&&i?r.merge(i).order():i}function sr(t){for(var e=t.selection?t.selection():t,n=this._groups,r=e._groups,i=n.length,o=r.length,a=Math.min(i,o),s=new Array(i),u=0;u<a;++u)for(var l=n[u],f=r[u],x=l.length,p=s[u]=new Array(x),h,_=0;_<x;++_)(h=l[_]||f[_])&&(p[_]=h);for(;u<i;++u)s[u]=n[u];return new H(s,this._parents)}function lr(){for(var t=this._groups,e=-1,n=t.length;++e<n;)for(var r=t[e],i=r.length-1,o=r[i],a;--i>=0;)(a=r[i])&&(o&&a.compareDocumentPosition(o)^4&&o.parentNode.insertBefore(a,o),o=a);return this}function ur(t){t||(t=vo);function e(x,p){return x&&p?t(x.__data__,p.__data__):!x-!p}for(var n=this._groups,r=n.length,i=new Array(r),o=0;o<r;++o){for(var a=n[o],s=a.length,u=i[o]=new Array(s),l,f=0;f<s;++f)(l=a[f])&&(u[f]=l);u.sort(e)}return new H(i,this._parents).order()}function vo(t,e){return t<e?-1:t>e?1:t>=e?0:NaN}function fr(){var t=arguments[0];return arguments[0]=this,t.apply(null,arguments),this}function cr(){return Array.from(this)}function pr(){for(var t=this._groups,e=0,n=t.length;e<n;++e)for(var r=t[e],i=0,o=r.length;i<o;++i){var a=r[i];if(a)return a}return null}function hr(){let t=0;for(let e of this)++t;return t}function mr(){return!this.node()}function dr(t){for(var e=this._groups,n=0,r=e.length;n<r;++n)for(var i=e[n],o=0,a=i.length,s;o<a;++o)(s=i[o])&&t.call(s,s.__data__,o,i);return this}function wo(t){return function(){this.removeAttribute(t)}}function _o(t){return function(){this.removeAttributeNS(t.space,t.local)}}function bo(t,e){return function(){this.setAttribute(t,e)}}function No(t,e){return function(){this.setAttributeNS(t.space,t.local,e)}}function ko(t,e){return function(){var n=e.apply(this,arguments);n==null?this.removeAttribute(t):this.setAttribute(t,n)}}function Eo(t,e){return function(){var n=e.apply(this,arguments);n==null?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,n)}}function gr(t,e){var n=gt(t);if(arguments.length<2){var r=this.node();return n.local?r.getAttributeNS(n.space,n.local):r.getAttribute(n)}return this.each((e==null?n.local?_o:wo:typeof e==\"function\"?n.local?Eo:ko:n.local?No:bo)(n,e))}function ve(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView}function Ao(t){return function(){this.style.removeProperty(t)}}function Mo(t,e,n){return function(){this.style.setProperty(t,e,n)}}function So(t,e,n){return function(){var r=e.apply(this,arguments);r==null?this.style.removeProperty(t):this.style.setProperty(t,r,n)}}function xr(t,e,n){return arguments.length>1?this.each((e==null?Ao:typeof e==\"function\"?So:Mo)(t,e,n==null?\"\":n)):vt(this.node(),t)}function vt(t,e){return t.style.getPropertyValue(e)||ve(t).getComputedStyle(t,null).getPropertyValue(e)}function To(t){return function(){delete this[t]}}function Co(t,e){return function(){this[t]=e}}function Io(t,e){return function(){var n=e.apply(this,arguments);n==null?delete this[t]:this[t]=n}}function yr(t,e){return arguments.length>1?this.each((e==null?To:typeof e==\"function\"?Io:Co)(t,e)):this.node()[t]}function vr(t){return t.trim().split(/^|\\s+/)}function Ue(t){return t.classList||new wr(t)}function wr(t){this._node=t,this._names=vr(t.getAttribute(\"class\")||\"\")}wr.prototype={add:function(t){var e=this._names.indexOf(t);e<0&&(this._names.push(t),this._node.setAttribute(\"class\",this._names.join(\" \")))},remove:function(t){var e=this._names.indexOf(t);e>=0&&(this._names.splice(e,1),this._node.setAttribute(\"class\",this._names.join(\" \")))},contains:function(t){return this._names.indexOf(t)>=0}};function _r(t,e){for(var n=Ue(t),r=-1,i=e.length;++r<i;)n.add(e[r])}function br(t,e){for(var n=Ue(t),r=-1,i=e.length;++r<i;)n.remove(e[r])}function zo(t){return function(){_r(this,t)}}function $o(t){return function(){br(this,t)}}function Oo(t,e){return function(){(e.apply(this,arguments)?_r:br)(this,t)}}function Nr(t,e){var n=vr(t+\"\");if(arguments.length<2){for(var r=Ue(this.node()),i=-1,o=n.length;++i<o;)if(!r.contains(n[i]))return!1;return!0}return this.each((typeof e==\"function\"?Oo:e?zo:$o)(n,e))}function Do(){this.textContent=\"\"}function Bo(t){return function(){this.textContent=t}}function Ro(t){return function(){var e=t.apply(this,arguments);this.textContent=e==null?\"\":e}}function kr(t){return arguments.length?this.each(t==null?Do:(typeof t==\"function\"?Ro:Bo)(t)):this.node().textContent}function Lo(){this.innerHTML=\"\"}function Po(t){return function(){this.innerHTML=t}}function Go(t){return function(){var e=t.apply(this,arguments);this.innerHTML=e==null?\"\":e}}function Er(t){return arguments.length?this.each(t==null?Lo:(typeof t==\"function\"?Go:Po)(t)):this.node().innerHTML}function Vo(){this.nextSibling&&this.parentNode.appendChild(this)}function Ar(){return this.each(Vo)}function Fo(){this.previousSibling&&this.parentNode.insertBefore(this,this.parentNode.firstChild)}function Mr(){return this.each(Fo)}function Sr(t){var e=typeof t==\"function\"?t:ge(t);return this.select(function(){return this.appendChild(e.apply(this,arguments))})}function Ho(){return null}function Tr(t,e){var n=typeof t==\"function\"?t:ge(t),r=e==null?Ho:typeof e==\"function\"?e:St(e);return this.select(function(){return this.insertBefore(n.apply(this,arguments),r.apply(this,arguments)||null)})}function Xo(){var t=this.parentNode;t&&t.removeChild(this)}function Cr(){return this.each(Xo)}function Yo(){var t=this.cloneNode(!1),e=this.parentNode;return e?e.insertBefore(t,this.nextSibling):t}function qo(){var t=this.cloneNode(!0),e=this.parentNode;return e?e.insertBefore(t,this.nextSibling):t}function Ir(t){return this.select(t?qo:Yo)}function zr(t){return arguments.length?this.property(\"__data__\",t):this.node().__data__}function Uo(t){return function(e){t.call(this,e,this.__data__)}}function Qo(t){return t.trim().split(/^|\\s+/).map(function(e){var n=\"\",r=e.indexOf(\".\");return r>=0&&(n=e.slice(r+1),e=e.slice(0,r)),{type:e,name:n}})}function Ko(t){return function(){var e=this.__on;if(e){for(var n=0,r=-1,i=e.length,o;n<i;++n)o=e[n],(!t.type||o.type===t.type)&&o.name===t.name?this.removeEventListener(o.type,o.listener,o.options):e[++r]=o;++r?e.length=r:delete this.__on}}}function Zo(t,e,n){return function(){var r=this.__on,i,o=Uo(e);if(r){for(var a=0,s=r.length;a<s;++a)if((i=r[a]).type===t.type&&i.name===t.name){this.removeEventListener(i.type,i.listener,i.options),this.addEventListener(i.type,i.listener=o,i.options=n),i.value=e;return}}this.addEventListener(t.type,o,n),i={type:t.type,name:t.name,value:e,listener:o,options:n},r?r.push(i):this.__on=[i]}}function $r(t,e,n){var r=Qo(t+\"\"),i,o=r.length,a;if(arguments.length<2){var s=this.node().__on;if(s){for(var u=0,l=s.length,f;u<l;++u)for(i=0,f=s[u];i<o;++i)if((a=r[i]).type===f.type&&a.name===f.name)return f.value}return}for(s=e?Zo:Ko,i=0;i<o;++i)this.each(s(r[i],e,n));return this}function Or(t,e,n){var r=ve(t),i=r.CustomEvent;typeof i==\"function\"?i=new i(e,n):(i=r.document.createEvent(\"Event\"),n?(i.initEvent(e,n.bubbles,n.cancelable),i.detail=n.detail):i.initEvent(e,!1,!1)),t.dispatchEvent(i)}function Wo(t,e){return function(){return Or(this,t,e)}}function Jo(t,e){return function(){return Or(this,t,e.apply(this,arguments))}}function Dr(t,e){return this.each((typeof e==\"function\"?Jo:Wo)(t,e))}function*Br(){for(var t=this._groups,e=0,n=t.length;e<n;++e)for(var r=t[e],i=0,o=r.length,a;i<o;++i)(a=r[i])&&(yield a)}var Qe=[null];function H(t,e){this._groups=t,this._parents=e}function Rr(){return new H([[document.documentElement]],Qe)}function jo(){return this}H.prototype=Rr.prototype={constructor:H,select:Wn,selectAll:Jn,selectChild:jn,selectChildren:tr,filter:er,data:ir,enter:nr,exit:or,join:ar,merge:sr,selection:jo,order:lr,sort:ur,call:fr,nodes:cr,node:pr,size:hr,empty:mr,each:dr,attr:gr,style:xr,property:yr,classed:Nr,text:kr,html:Er,raise:Ar,lower:Mr,append:Sr,insert:Tr,remove:Cr,clone:Ir,datum:zr,on:$r,dispatch:Dr,[Symbol.iterator]:Br};var xt=Rr;function V(t){return typeof t==\"string\"?new H([[document.querySelector(t)]],[document.documentElement]):new H([[t]],Qe)}function Lr(t){let e;for(;e=t.sourceEvent;)t=e;return t}function ot(t,e){if(t=Lr(t),e===void 0&&(e=t.currentTarget),e){var n=e.ownerSVGElement||e;if(n.createSVGPoint){var r=n.createSVGPoint();return r.x=t.clientX,r.y=t.clientY,r=r.matrixTransform(e.getScreenCTM().inverse()),[r.x,r.y]}if(e.getBoundingClientRect){var i=e.getBoundingClientRect();return[t.clientX-i.left-e.clientLeft,t.clientY-i.top-e.clientTop]}}return[t.pageX,t.pageY]}var Pr={passive:!1},Tt={capture:!0,passive:!1};function we(t){t.stopImmediatePropagation()}function wt(t){t.preventDefault(),t.stopImmediatePropagation()}function Wt(t){var e=t.document.documentElement,n=V(t).on(\"dragstart.drag\",wt,Tt);\"onselectstart\"in e?n.on(\"selectstart.drag\",wt,Tt):(e.__noselect=e.style.MozUserSelect,e.style.MozUserSelect=\"none\")}function Jt(t,e){var n=t.document.documentElement,r=V(t).on(\"dragstart.drag\",null);e&&(r.on(\"click.drag\",wt,Tt),setTimeout(function(){r.on(\"click.drag\",null)},0)),\"onselectstart\"in n?r.on(\"selectstart.drag\",null):(n.style.MozUserSelect=n.__noselect,delete n.__noselect)}var jt=t=>()=>t;function te(t,{sourceEvent:e,subject:n,target:r,identifier:i,active:o,x:a,y:s,dx:u,dy:l,dispatch:f}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:e,enumerable:!0,configurable:!0},subject:{value:n,enumerable:!0,configurable:!0},target:{value:r,enumerable:!0,configurable:!0},identifier:{value:i,enumerable:!0,configurable:!0},active:{value:o,enumerable:!0,configurable:!0},x:{value:a,enumerable:!0,configurable:!0},y:{value:s,enumerable:!0,configurable:!0},dx:{value:u,enumerable:!0,configurable:!0},dy:{value:l,enumerable:!0,configurable:!0},_:{value:f}})}te.prototype.on=function(){var t=this._.on.apply(this._,arguments);return t===this._?this:t};function ta(t){return!t.ctrlKey&&!t.button}function ea(){return this.parentNode}function na(t,e){return e==null?{x:t.x,y:t.y}:e}function ra(){return navigator.maxTouchPoints||\"ontouchstart\"in this}function Ke(){var t=ta,e=ea,n=na,r=ra,i={},o=dt(\"start\",\"drag\",\"end\"),a=0,s,u,l,f,x=0;function p(m){m.on(\"mousedown.drag\",h).filter(r).on(\"touchstart.drag\",y).on(\"touchmove.drag\",d,Pr).on(\"touchend.drag touchcancel.drag\",b).style(\"touch-action\",\"none\").style(\"-webkit-tap-highlight-color\",\"rgba(0,0,0,0)\")}function h(m,E){if(!(f||!t.call(this,m,E))){var T=A(this,e.call(this,m,E),m,E,\"mouse\");T&&(V(m.view).on(\"mousemove.drag\",_,Tt).on(\"mouseup.drag\",g,Tt),Wt(m.view),we(m),l=!1,s=m.clientX,u=m.clientY,T(\"start\",m))}}function _(m){if(wt(m),!l){var E=m.clientX-s,T=m.clientY-u;l=E*E+T*T>x}i.mouse(\"drag\",m)}function g(m){V(m.view).on(\"mousemove.drag mouseup.drag\",null),Jt(m.view,l),wt(m),i.mouse(\"end\",m)}function y(m,E){if(t.call(this,m,E)){var T=m.changedTouches,I=e.call(this,m,E),$=T.length,L,O;for(L=0;L<$;++L)(O=A(this,I,m,E,T[L].identifier,T[L]))&&(we(m),O(\"start\",m,T[L]))}}function d(m){var E=m.changedTouches,T=E.length,I,$;for(I=0;I<T;++I)($=i[E[I].identifier])&&(wt(m),$(\"drag\",m,E[I]))}function b(m){var E=m.changedTouches,T=E.length,I,$;for(f&&clearTimeout(f),f=setTimeout(function(){f=null},500),I=0;I<T;++I)($=i[E[I].identifier])&&(we(m),$(\"end\",m,E[I]))}function A(m,E,T,I,$,L){var O=o.copy(),F=ot(L||T,E),rt,Z,c;if((c=n.call(m,new te(\"beforestart\",{sourceEvent:T,target:p,identifier:$,active:a,x:F[0],y:F[1],dx:0,dy:0,dispatch:O}),I))!=null)return rt=c.x-F[0]||0,Z=c.y-F[1]||0,function k(w,N,C){var z=F,S;switch(w){case\"start\":i[$]=k,S=a++;break;case\"end\":delete i[$],--a;case\"drag\":F=ot(C||N,E),S=a;break}O.call(w,m,new te(w,{sourceEvent:N,subject:c,target:p,identifier:$,active:S,x:F[0]+rt,y:F[1]+Z,dx:F[0]-z[0],dy:F[1]-z[1],dispatch:O}),I)}}return p.filter=function(m){return arguments.length?(t=typeof m==\"function\"?m:jt(!!m),p):t},p.container=function(m){return arguments.length?(e=typeof m==\"function\"?m:jt(m),p):e},p.subject=function(m){return arguments.length?(n=typeof m==\"function\"?m:jt(m),p):n},p.touchable=function(m){return arguments.length?(r=typeof m==\"function\"?m:jt(!!m),p):r},p.on=function(){var m=o.on.apply(o,arguments);return m===o?p:m},p.clickDistance=function(m){return arguments.length?(x=(m=+m)*m,p):Math.sqrt(x)},p}function _e(t,e,n){t.prototype=e.prototype=n,n.constructor=t}function Ze(t,e){var n=Object.create(t.prototype);for(var r in e)n[r]=e[r];return n}function re(){}var ee=.7,ke=1/ee,Lt=\"\\\\s*([+-]?\\\\d+)\\\\s*\",ne=\"\\\\s*([+-]?(?:\\\\d*\\\\.)?\\\\d+(?:[eE][+-]?\\\\d+)?)\\\\s*\",pt=\"\\\\s*([+-]?(?:\\\\d*\\\\.)?\\\\d+(?:[eE][+-]?\\\\d+)?)%\\\\s*\",ia=/^#([0-9a-f]{3,8})$/,oa=new RegExp(`^rgb\\\\(${Lt},${Lt},${Lt}\\\\)$`),aa=new RegExp(`^rgb\\\\(${pt},${pt},${pt}\\\\)$`),sa=new RegExp(`^rgba\\\\(${Lt},${Lt},${Lt},${ne}\\\\)$`),la=new RegExp(`^rgba\\\\(${pt},${pt},${pt},${ne}\\\\)$`),ua=new RegExp(`^hsl\\\\(${ne},${pt},${pt}\\\\)$`),fa=new RegExp(`^hsla\\\\(${ne},${pt},${pt},${ne}\\\\)$`),Gr={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074};_e(re,_t,{copy(t){return Object.assign(new this.constructor,this,t)},displayable(){return this.rgb().displayable()},hex:Vr,formatHex:Vr,formatHex8:ca,formatHsl:pa,formatRgb:Fr,toString:Fr});function Vr(){return this.rgb().formatHex()}function ca(){return this.rgb().formatHex8()}function pa(){return Qr(this).formatHsl()}function Fr(){return this.rgb().formatRgb()}function _t(t){var e,n;return t=(t+\"\").trim().toLowerCase(),(e=ia.exec(t))?(n=e[1].length,e=parseInt(e[1],16),n===6?Hr(e):n===3?new it(e>>8&15|e>>4&240,e>>4&15|e&240,(e&15)<<4|e&15,1):n===8?be(e>>24&255,e>>16&255,e>>8&255,(e&255)/255):n===4?be(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|e&240,((e&15)<<4|e&15)/255):null):(e=oa.exec(t))?new it(e[1],e[2],e[3],1):(e=aa.exec(t))?new it(e[1]*255/100,e[2]*255/100,e[3]*255/100,1):(e=sa.exec(t))?be(e[1],e[2],e[3],e[4]):(e=la.exec(t))?be(e[1]*255/100,e[2]*255/100,e[3]*255/100,e[4]):(e=ua.exec(t))?qr(e[1],e[2]/100,e[3]/100,1):(e=fa.exec(t))?qr(e[1],e[2]/100,e[3]/100,e[4]):Gr.hasOwnProperty(t)?Hr(Gr[t]):t===\"transparent\"?new it(NaN,NaN,NaN,0):null}function Hr(t){return new it(t>>16&255,t>>8&255,t&255,1)}function be(t,e,n,r){return r<=0&&(t=e=n=NaN),new it(t,e,n,r)}function ha(t){return t instanceof re||(t=_t(t)),t?(t=t.rgb(),new it(t.r,t.g,t.b,t.opacity)):new it}function Pt(t,e,n,r){return arguments.length===1?ha(t):new it(t,e,n,r==null?1:r)}function it(t,e,n,r){this.r=+t,this.g=+e,this.b=+n,this.opacity=+r}_e(it,Pt,Ze(re,{brighter(t){return t=t==null?ke:Math.pow(ke,t),new it(this.r*t,this.g*t,this.b*t,this.opacity)},darker(t){return t=t==null?ee:Math.pow(ee,t),new it(this.r*t,this.g*t,this.b*t,this.opacity)},rgb(){return this},clamp(){return new it(It(this.r),It(this.g),It(this.b),Ee(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:Xr,formatHex:Xr,formatHex8:ma,formatRgb:Yr,toString:Yr}));function Xr(){return`#${Ct(this.r)}${Ct(this.g)}${Ct(this.b)}`}function ma(){return`#${Ct(this.r)}${Ct(this.g)}${Ct(this.b)}${Ct((isNaN(this.opacity)?1:this.opacity)*255)}`}function Yr(){let t=Ee(this.opacity);return`${t===1?\"rgb(\":\"rgba(\"}${It(this.r)}, ${It(this.g)}, ${It(this.b)}${t===1?\")\":`, ${t})`}`}function Ee(t){return isNaN(t)?1:Math.max(0,Math.min(1,t))}function It(t){return Math.max(0,Math.min(255,Math.round(t)||0))}function Ct(t){return t=It(t),(t<16?\"0\":\"\")+t.toString(16)}function qr(t,e,n,r){return r<=0?t=e=n=NaN:n<=0||n>=1?t=e=NaN:e<=0&&(t=NaN),new ut(t,e,n,r)}function Qr(t){if(t instanceof ut)return new ut(t.h,t.s,t.l,t.opacity);if(t instanceof re||(t=_t(t)),!t)return new ut;if(t instanceof ut)return t;t=t.rgb();var e=t.r/255,n=t.g/255,r=t.b/255,i=Math.min(e,n,r),o=Math.max(e,n,r),a=NaN,s=o-i,u=(o+i)/2;return s?(e===o?a=(n-r)/s+(n<r)*6:n===o?a=(r-e)/s+2:a=(e-n)/s+4,s/=u<.5?o+i:2-o-i,a*=60):s=u>0&&u<1?0:a,new ut(a,s,u,t.opacity)}function Kr(t,e,n,r){return arguments.length===1?Qr(t):new ut(t,e,n,r==null?1:r)}function ut(t,e,n,r){this.h=+t,this.s=+e,this.l=+n,this.opacity=+r}_e(ut,Kr,Ze(re,{brighter(t){return t=t==null?ke:Math.pow(ke,t),new ut(this.h,this.s,this.l*t,this.opacity)},darker(t){return t=t==null?ee:Math.pow(ee,t),new ut(this.h,this.s,this.l*t,this.opacity)},rgb(){var t=this.h%360+(this.h<0)*360,e=isNaN(t)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*e,i=2*n-r;return new it(We(t>=240?t-240:t+120,i,r),We(t,i,r),We(t<120?t+240:t-120,i,r),this.opacity)},clamp(){return new ut(Ur(this.h),Ne(this.s),Ne(this.l),Ee(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){let t=Ee(this.opacity);return`${t===1?\"hsl(\":\"hsla(\"}${Ur(this.h)}, ${Ne(this.s)*100}%, ${Ne(this.l)*100}%${t===1?\")\":`, ${t})`}`}}));function Ur(t){return t=(t||0)%360,t<0?t+360:t}function Ne(t){return Math.max(0,Math.min(1,t||0))}function We(t,e,n){return(t<60?e+(n-e)*t/60:t<180?n:t<240?e+(n-e)*(240-t)/60:e)*255}function Je(t,e,n,r,i){var o=t*t,a=o*t;return((1-3*t+3*o-a)*e+(4-6*o+3*a)*n+(1+3*t+3*o-3*a)*r+a*i)/6}function Zr(t){var e=t.length-1;return function(n){var r=n<=0?n=0:n>=1?(n=1,e-1):Math.floor(n*e),i=t[r],o=t[r+1],a=r>0?t[r-1]:2*i-o,s=r<e-1?t[r+2]:2*o-i;return Je((n-r/e)*e,a,i,o,s)}}function Wr(t){var e=t.length;return function(n){var r=Math.floor(((n%=1)<0?++n:n)*e),i=t[(r+e-1)%e],o=t[r%e],a=t[(r+1)%e],s=t[(r+2)%e];return Je((n-r/e)*e,i,o,a,s)}}var je=t=>()=>t;function da(t,e){return function(n){return t+n*e}}function ga(t,e,n){return t=Math.pow(t,n),e=Math.pow(e,n)-t,n=1/n,function(r){return Math.pow(t+r*e,n)}}function Jr(t){return(t=+t)==1?Ae:function(e,n){return n-e?ga(e,n,t):je(isNaN(e)?n:e)}}function Ae(t,e){var n=e-t;return n?da(t,n):je(isNaN(t)?e:t)}var Me=(function t(e){var n=Jr(e);function r(i,o){var a=n((i=Pt(i)).r,(o=Pt(o)).r),s=n(i.g,o.g),u=n(i.b,o.b),l=Ae(i.opacity,o.opacity);return function(f){return i.r=a(f),i.g=s(f),i.b=u(f),i.opacity=l(f),i+\"\"}}return r.gamma=t,r})(1);function jr(t){return function(e){var n=e.length,r=new Array(n),i=new Array(n),o=new Array(n),a,s;for(a=0;a<n;++a)s=Pt(e[a]),r[a]=s.r||0,i[a]=s.g||0,o[a]=s.b||0;return r=t(r),i=t(i),o=t(o),s.opacity=1,function(u){return s.r=r(u),s.g=i(u),s.b=o(u),s+\"\"}}}var xa=jr(Zr),ya=jr(Wr);function at(t,e){return t=+t,e=+e,function(n){return t*(1-n)+e*n}}var en=/[-+]?(?:\\d+\\.?\\d*|\\.?\\d+)(?:[eE][-+]?\\d+)?/g,tn=new RegExp(en.source,\"g\");function va(t){return function(){return t}}function wa(t){return function(e){return t(e)+\"\"}}function nn(t,e){var n=en.lastIndex=tn.lastIndex=0,r,i,o,a=-1,s=[],u=[];for(t=t+\"\",e=e+\"\";(r=en.exec(t))&&(i=tn.exec(e));)(o=i.index)>n&&(o=e.slice(n,o),s[a]?s[a]+=o:s[++a]=o),(r=r[0])===(i=i[0])?s[a]?s[a]+=i:s[++a]=i:(s[++a]=null,u.push({i:a,x:at(r,i)})),n=tn.lastIndex;return n<e.length&&(o=e.slice(n),s[a]?s[a]+=o:s[++a]=o),s.length<2?u[0]?wa(u[0].x):va(e):(e=u.length,function(l){for(var f=0,x;f<e;++f)s[(x=u[f]).i]=x.x(l);return s.join(\"\")})}var ti=180/Math.PI,Se={translateX:0,translateY:0,rotate:0,skewX:0,scaleX:1,scaleY:1};function rn(t,e,n,r,i,o){var a,s,u;return(a=Math.sqrt(t*t+e*e))&&(t/=a,e/=a),(u=t*n+e*r)&&(n-=t*u,r-=e*u),(s=Math.sqrt(n*n+r*r))&&(n/=s,r/=s,u/=s),t*r<e*n&&(t=-t,e=-e,u=-u,a=-a),{translateX:i,translateY:o,rotate:Math.atan2(e,t)*ti,skewX:Math.atan(u)*ti,scaleX:a,scaleY:s}}var Te;function ei(t){let e=new(typeof DOMMatrix==\"function\"?DOMMatrix:WebKitCSSMatrix)(t+\"\");return e.isIdentity?Se:rn(e.a,e.b,e.c,e.d,e.e,e.f)}function ni(t){return t==null?Se:(Te||(Te=document.createElementNS(\"http://www.w3.org/2000/svg\",\"g\")),Te.setAttribute(\"transform\",t),(t=Te.transform.baseVal.consolidate())?(t=t.matrix,rn(t.a,t.b,t.c,t.d,t.e,t.f)):Se)}function ri(t,e,n,r){function i(l){return l.length?l.pop()+\" \":\"\"}function o(l,f,x,p,h,_){if(l!==x||f!==p){var g=h.push(\"translate(\",null,e,null,n);_.push({i:g-4,x:at(l,x)},{i:g-2,x:at(f,p)})}else(x||p)&&h.push(\"translate(\"+x+e+p+n)}function a(l,f,x,p){l!==f?(l-f>180?f+=360:f-l>180&&(l+=360),p.push({i:x.push(i(x)+\"rotate(\",null,r)-2,x:at(l,f)})):f&&x.push(i(x)+\"rotate(\"+f+r)}function s(l,f,x,p){l!==f?p.push({i:x.push(i(x)+\"skewX(\",null,r)-2,x:at(l,f)}):f&&x.push(i(x)+\"skewX(\"+f+r)}function u(l,f,x,p,h,_){if(l!==x||f!==p){var g=h.push(i(h)+\"scale(\",null,\",\",null,\")\");_.push({i:g-4,x:at(l,x)},{i:g-2,x:at(f,p)})}else(x!==1||p!==1)&&h.push(i(h)+\"scale(\"+x+\",\"+p+\")\")}return function(l,f){var x=[],p=[];return l=t(l),f=t(f),o(l.translateX,l.translateY,f.translateX,f.translateY,x,p),a(l.rotate,f.rotate,x,p),s(l.skewX,f.skewX,x,p),u(l.scaleX,l.scaleY,f.scaleX,f.scaleY,x,p),l=f=null,function(h){for(var _=-1,g=p.length,y;++_<g;)x[(y=p[_]).i]=y.x(h);return x.join(\"\")}}}var on=ri(ei,\"px, \",\"px)\",\"deg)\"),an=ri(ni,\", \",\")\",\")\");var _a=1e-12;function ii(t){return((t=Math.exp(t))+1/t)/2}function ba(t){return((t=Math.exp(t))-1/t)/2}function Na(t){return((t=Math.exp(2*t))-1)/(t+1)}var sn=(function t(e,n,r){function i(o,a){var s=o[0],u=o[1],l=o[2],f=a[0],x=a[1],p=a[2],h=f-s,_=x-u,g=h*h+_*_,y,d;if(g<_a)d=Math.log(p/l)/e,y=function(I){return[s+I*h,u+I*_,l*Math.exp(e*I*d)]};else{var b=Math.sqrt(g),A=(p*p-l*l+r*g)/(2*l*n*b),m=(p*p-l*l-r*g)/(2*p*n*b),E=Math.log(Math.sqrt(A*A+1)-A),T=Math.log(Math.sqrt(m*m+1)-m);d=(T-E)/e,y=function(I){var $=I*d,L=ii(E),O=l/(n*b)*(L*Na(e*$+E)-ba(E));return[s+O*h,u+O*_,l*L/ii(e*$+E)]}}return y.duration=d*1e3*e/Math.SQRT2,y}return i.rho=function(o){var a=Math.max(.001,+o),s=a*a,u=s*s;return t(a,s,u)},i})(Math.SQRT2,2,4);var ka=dt(\"start\",\"end\",\"cancel\",\"interrupt\"),Ea=[],si=0,oi=1,Ie=2,Ce=3,ai=4,ze=5,ie=6;function bt(t,e,n,r,i,o){var a=t.__transition;if(!a)t.__transition={};else if(n in a)return;Aa(t,n,{name:e,index:r,group:i,on:ka,tween:Ea,time:o.time,delay:o.delay,duration:o.duration,ease:o.ease,timer:null,state:si})}function oe(t,e){var n=Y(t,e);if(n.state>si)throw new Error(\"too late; already scheduled\");return n}function J(t,e){var n=Y(t,e);if(n.state>Ce)throw new Error(\"too late; already running\");return n}function Y(t,e){var n=t.__transition;if(!n||!(n=n[e]))throw new Error(\"transition not found\");return n}function Aa(t,e,n){var r=t.__transition,i;r[e]=n,n.timer=Rt(o,0,n.time);function o(l){n.state=oi,n.timer.restart(a,n.delay,n.time),n.delay<=l&&a(l-n.delay)}function a(l){var f,x,p,h;if(n.state!==oi)return u();for(f in r)if(h=r[f],h.name===n.name){if(h.state===Ce)return me(a);h.state===ai?(h.state=ie,h.timer.stop(),h.on.call(\"interrupt\",t,t.__data__,h.index,h.group),delete r[f]):+f<e&&(h.state=ie,h.timer.stop(),h.on.call(\"cancel\",t,t.__data__,h.index,h.group),delete r[f])}if(me(function(){n.state===Ce&&(n.state=ai,n.timer.restart(s,n.delay,n.time),s(l))}),n.state=Ie,n.on.call(\"start\",t,t.__data__,n.index,n.group),n.state===Ie){for(n.state=Ce,i=new Array(p=n.tween.length),f=0,x=-1;f<p;++f)(h=n.tween[f].value.call(t,t.__data__,n.index,n.group))&&(i[++x]=h);i.length=x+1}}function s(l){for(var f=l<n.duration?n.ease.call(null,l/n.duration):(n.timer.restart(u),n.state=ze,1),x=-1,p=i.length;++x<p;)i[x].call(t,f);n.state===ze&&(n.on.call(\"end\",t,t.__data__,n.index,n.group),u())}function u(){n.state=ie,n.timer.stop(),delete r[e];for(var l in r)return;delete t.__transition}}function zt(t,e){var n=t.__transition,r,i,o=!0,a;if(n){e=e==null?null:e+\"\";for(a in n){if((r=n[a]).name!==e){o=!1;continue}i=r.state>Ie&&r.state<ze,r.state=ie,r.timer.stop(),r.on.call(i?\"interrupt\":\"cancel\",t,t.__data__,r.index,r.group),delete n[a]}o&&delete t.__transition}}function li(t){return this.each(function(){zt(this,t)})}function Ma(t,e){var n,r;return function(){var i=J(this,t),o=i.tween;if(o!==n){r=n=o;for(var a=0,s=r.length;a<s;++a)if(r[a].name===e){r=r.slice(),r.splice(a,1);break}}i.tween=r}}function Sa(t,e,n){var r,i;if(typeof n!=\"function\")throw new Error;return function(){var o=J(this,t),a=o.tween;if(a!==r){i=(r=a).slice();for(var s={name:e,value:n},u=0,l=i.length;u<l;++u)if(i[u].name===e){i[u]=s;break}u===l&&i.push(s)}o.tween=i}}function ui(t,e){var n=this._id;if(t+=\"\",arguments.length<2){for(var r=Y(this.node(),n).tween,i=0,o=r.length,a;i<o;++i)if((a=r[i]).name===t)return a.value;return null}return this.each((e==null?Ma:Sa)(n,t,e))}function Gt(t,e,n){var r=t._id;return t.each(function(){var i=J(this,r);(i.value||(i.value={}))[e]=n.apply(this,arguments)}),function(i){return Y(i,r).value[e]}}function $e(t,e){var n;return(typeof e==\"number\"?at:e instanceof _t?Me:(n=_t(e))?(e=n,Me):nn)(t,e)}function Ta(t){return function(){this.removeAttribute(t)}}function Ca(t){return function(){this.removeAttributeNS(t.space,t.local)}}function Ia(t,e,n){var r,i=n+\"\",o;return function(){var a=this.getAttribute(t);return a===i?null:a===r?o:o=e(r=a,n)}}function za(t,e,n){var r,i=n+\"\",o;return function(){var a=this.getAttributeNS(t.space,t.local);return a===i?null:a===r?o:o=e(r=a,n)}}function $a(t,e,n){var r,i,o;return function(){var a,s=n(this),u;return s==null?void this.removeAttribute(t):(a=this.getAttribute(t),u=s+\"\",a===u?null:a===r&&u===i?o:(i=u,o=e(r=a,s)))}}function Oa(t,e,n){var r,i,o;return function(){var a,s=n(this),u;return s==null?void this.removeAttributeNS(t.space,t.local):(a=this.getAttributeNS(t.space,t.local),u=s+\"\",a===u?null:a===r&&u===i?o:(i=u,o=e(r=a,s)))}}function fi(t,e){var n=gt(t),r=n===\"transform\"?an:$e;return this.attrTween(t,typeof e==\"function\"?(n.local?Oa:$a)(n,r,Gt(this,\"attr.\"+t,e)):e==null?(n.local?Ca:Ta)(n):(n.local?za:Ia)(n,r,e))}function Da(t,e){return function(n){this.setAttribute(t,e.call(this,n))}}function Ba(t,e){return function(n){this.setAttributeNS(t.space,t.local,e.call(this,n))}}function Ra(t,e){var n,r;function i(){var o=e.apply(this,arguments);return o!==r&&(n=(r=o)&&Ba(t,o)),n}return i._value=e,i}function La(t,e){var n,r;function i(){var o=e.apply(this,arguments);return o!==r&&(n=(r=o)&&Da(t,o)),n}return i._value=e,i}function ci(t,e){var n=\"attr.\"+t;if(arguments.length<2)return(n=this.tween(n))&&n._value;if(e==null)return this.tween(n,null);if(typeof e!=\"function\")throw new Error;var r=gt(t);return this.tween(n,(r.local?Ra:La)(r,e))}function Pa(t,e){return function(){oe(this,t).delay=+e.apply(this,arguments)}}function Ga(t,e){return e=+e,function(){oe(this,t).delay=e}}function pi(t){var e=this._id;return arguments.length?this.each((typeof t==\"function\"?Pa:Ga)(e,t)):Y(this.node(),e).delay}function Va(t,e){return function(){J(this,t).duration=+e.apply(this,arguments)}}function Fa(t,e){return e=+e,function(){J(this,t).duration=e}}function hi(t){var e=this._id;return arguments.length?this.each((typeof t==\"function\"?Va:Fa)(e,t)):Y(this.node(),e).duration}function Ha(t,e){if(typeof e!=\"function\")throw new Error;return function(){J(this,t).ease=e}}function mi(t){var e=this._id;return arguments.length?this.each(Ha(e,t)):Y(this.node(),e).ease}function Xa(t,e){return function(){var n=e.apply(this,arguments);if(typeof n!=\"function\")throw new Error;J(this,t).ease=n}}function di(t){if(typeof t!=\"function\")throw new Error;return this.each(Xa(this._id,t))}function gi(t){typeof t!=\"function\"&&(t=Kt(t));for(var e=this._groups,n=e.length,r=new Array(n),i=0;i<n;++i)for(var o=e[i],a=o.length,s=r[i]=[],u,l=0;l<a;++l)(u=o[l])&&t.call(u,u.__data__,l,o)&&s.push(u);return new nt(r,this._parents,this._name,this._id)}function xi(t){if(t._id!==this._id)throw new Error;for(var e=this._groups,n=t._groups,r=e.length,i=n.length,o=Math.min(r,i),a=new Array(r),s=0;s<o;++s)for(var u=e[s],l=n[s],f=u.length,x=a[s]=new Array(f),p,h=0;h<f;++h)(p=u[h]||l[h])&&(x[h]=p);for(;s<r;++s)a[s]=e[s];return new nt(a,this._parents,this._name,this._id)}function Ya(t){return(t+\"\").trim().split(/^|\\s+/).every(function(e){var n=e.indexOf(\".\");return n>=0&&(e=e.slice(0,n)),!e||e===\"start\"})}function qa(t,e,n){var r,i,o=Ya(e)?oe:J;return function(){var a=o(this,t),s=a.on;s!==r&&(i=(r=s).copy()).on(e,n),a.on=i}}function yi(t,e){var n=this._id;return arguments.length<2?Y(this.node(),n).on.on(t):this.each(qa(n,t,e))}function Ua(t){return function(){var e=this.parentNode;for(var n in this.__transition)if(+n!==t)return;e&&e.removeChild(this)}}function vi(){return this.on(\"end.remove\",Ua(this._id))}function wi(t){var e=this._name,n=this._id;typeof t!=\"function\"&&(t=St(t));for(var r=this._groups,i=r.length,o=new Array(i),a=0;a<i;++a)for(var s=r[a],u=s.length,l=o[a]=new Array(u),f,x,p=0;p<u;++p)(f=s[p])&&(x=t.call(f,f.__data__,p,s))&&(\"__data__\"in f&&(x.__data__=f.__data__),l[p]=x,bt(l[p],e,n,p,l,Y(f,n)));return new nt(o,this._parents,e,n)}function _i(t){var e=this._name,n=this._id;typeof t!=\"function\"&&(t=Qt(t));for(var r=this._groups,i=r.length,o=[],a=[],s=0;s<i;++s)for(var u=r[s],l=u.length,f,x=0;x<l;++x)if(f=u[x]){for(var p=t.call(f,f.__data__,x,u),h,_=Y(f,n),g=0,y=p.length;g<y;++g)(h=p[g])&&bt(h,e,n,g,p,_);o.push(p),a.push(f)}return new nt(o,a,e,n)}var Qa=xt.prototype.constructor;function bi(){return new Qa(this._groups,this._parents)}function Ka(t,e){var n,r,i;return function(){var o=vt(this,t),a=(this.style.removeProperty(t),vt(this,t));return o===a?null:o===n&&a===r?i:i=e(n=o,r=a)}}function Ni(t){return function(){this.style.removeProperty(t)}}function Za(t,e,n){var r,i=n+\"\",o;return function(){var a=vt(this,t);return a===i?null:a===r?o:o=e(r=a,n)}}function Wa(t,e,n){var r,i,o;return function(){var a=vt(this,t),s=n(this),u=s+\"\";return s==null&&(u=s=(this.style.removeProperty(t),vt(this,t))),a===u?null:a===r&&u===i?o:(i=u,o=e(r=a,s))}}function Ja(t,e){var n,r,i,o=\"style.\"+e,a=\"end.\"+o,s;return function(){var u=J(this,t),l=u.on,f=u.value[o]==null?s||(s=Ni(e)):void 0;(l!==n||i!==f)&&(r=(n=l).copy()).on(a,i=f),u.on=r}}function ki(t,e,n){var r=(t+=\"\")==\"transform\"?on:$e;return e==null?this.styleTween(t,Ka(t,r)).on(\"end.style.\"+t,Ni(t)):typeof e==\"function\"?this.styleTween(t,Wa(t,r,Gt(this,\"style.\"+t,e))).each(Ja(this._id,t)):this.styleTween(t,Za(t,r,e),n).on(\"end.style.\"+t,null)}function ja(t,e,n){return function(r){this.style.setProperty(t,e.call(this,r),n)}}function ts(t,e,n){var r,i;function o(){var a=e.apply(this,arguments);return a!==i&&(r=(i=a)&&ja(t,a,n)),r}return o._value=e,o}function Ei(t,e,n){var r=\"style.\"+(t+=\"\");if(arguments.length<2)return(r=this.tween(r))&&r._value;if(e==null)return this.tween(r,null);if(typeof e!=\"function\")throw new Error;return this.tween(r,ts(t,e,n==null?\"\":n))}function es(t){return function(){this.textContent=t}}function ns(t){return function(){var e=t(this);this.textContent=e==null?\"\":e}}function Ai(t){return this.tween(\"text\",typeof t==\"function\"?ns(Gt(this,\"text\",t)):es(t==null?\"\":t+\"\"))}function rs(t){return function(e){this.textContent=t.call(this,e)}}function is(t){var e,n;function r(){var i=t.apply(this,arguments);return i!==n&&(e=(n=i)&&rs(i)),e}return r._value=t,r}function Mi(t){var e=\"text\";if(arguments.length<1)return(e=this.tween(e))&&e._value;if(t==null)return this.tween(e,null);if(typeof t!=\"function\")throw new Error;return this.tween(e,is(t))}function Si(){for(var t=this._name,e=this._id,n=Oe(),r=this._groups,i=r.length,o=0;o<i;++o)for(var a=r[o],s=a.length,u,l=0;l<s;++l)if(u=a[l]){var f=Y(u,e);bt(u,t,n,l,a,{time:f.time+f.delay+f.duration,delay:0,duration:f.duration,ease:f.ease})}return new nt(r,this._parents,t,n)}function Ti(){var t,e,n=this,r=n._id,i=n.size();return new Promise(function(o,a){var s={value:a},u={value:function(){--i===0&&o()}};n.each(function(){var l=J(this,r),f=l.on;f!==t&&(e=(t=f).copy(),e._.cancel.push(s),e._.interrupt.push(s),e._.end.push(u)),l.on=e}),i===0&&o()})}var os=0;function nt(t,e,n,r){this._groups=t,this._parents=e,this._name=n,this._id=r}function Ci(t){return xt().transition(t)}function Oe(){return++os}var yt=xt.prototype;nt.prototype=Ci.prototype={constructor:nt,select:wi,selectAll:_i,selectChild:yt.selectChild,selectChildren:yt.selectChildren,filter:gi,merge:xi,selection:bi,transition:Si,call:yt.call,nodes:yt.nodes,node:yt.node,size:yt.size,empty:yt.empty,each:yt.each,on:yi,attr:fi,attrTween:ci,style:ki,styleTween:Ei,text:Ai,textTween:Mi,remove:vi,tween:ui,delay:pi,duration:hi,ease:mi,easeVarying:di,end:Ti,[Symbol.iterator]:yt[Symbol.iterator]};function De(t){return((t*=2)<=1?t*t*t:(t-=2)*t*t+2)/2}var as={time:null,delay:0,duration:250,ease:De};function ss(t,e){for(var n;!(n=t.__transition)||!(n=n[e]);)if(!(t=t.parentNode))throw new Error(`transition ${e} not found`);return n}function Ii(t){var e,n;t instanceof nt?(e=t._id,t=t._name):(e=Oe(),(n=as).time=Ut(),t=t==null?null:t+\"\");for(var r=this._groups,i=r.length,o=0;o<i;++o)for(var a=r[o],s=a.length,u,l=0;l<s;++l)(u=a[l])&&bt(u,t,e,l,a,n||ss(u,e));return new nt(r,this._parents,t,e)}xt.prototype.interrupt=li;xt.prototype.transition=Ii;var ae=t=>()=>t;function ln(t,{sourceEvent:e,target:n,transform:r,dispatch:i}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:e,enumerable:!0,configurable:!0},target:{value:n,enumerable:!0,configurable:!0},transform:{value:r,enumerable:!0,configurable:!0},_:{value:i}})}function ft(t,e,n){this.k=t,this.x=e,this.y=n}ft.prototype={constructor:ft,scale:function(t){return t===1?this:new ft(this.k*t,this.x,this.y)},translate:function(t,e){return t===0&e===0?this:new ft(this.k,this.x+this.k*t,this.y+this.k*e)},apply:function(t){return[t[0]*this.k+this.x,t[1]*this.k+this.y]},applyX:function(t){return t*this.k+this.x},applyY:function(t){return t*this.k+this.y},invert:function(t){return[(t[0]-this.x)/this.k,(t[1]-this.y)/this.k]},invertX:function(t){return(t-this.x)/this.k},invertY:function(t){return(t-this.y)/this.k},rescaleX:function(t){return t.copy().domain(t.range().map(this.invertX,this).map(t.invert,t))},rescaleY:function(t){return t.copy().domain(t.range().map(this.invertY,this).map(t.invert,t))},toString:function(){return\"translate(\"+this.x+\",\"+this.y+\") scale(\"+this.k+\")\"}};var Nt=new ft(1,0,0);un.prototype=ft.prototype;function un(t){for(;!t.__zoom;)if(!(t=t.parentNode))return Nt;return t.__zoom}function Be(t){t.stopImmediatePropagation()}function Vt(t){t.preventDefault(),t.stopImmediatePropagation()}function ls(t){return(!t.ctrlKey||t.type===\"wheel\")&&!t.button}function us(){var t=this;return t instanceof SVGElement?(t=t.ownerSVGElement||t,t.hasAttribute(\"viewBox\")?(t=t.viewBox.baseVal,[[t.x,t.y],[t.x+t.width,t.y+t.height]]):[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]):[[0,0],[t.clientWidth,t.clientHeight]]}function zi(){return this.__zoom||Nt}function fs(t){return-t.deltaY*(t.deltaMode===1?.05:t.deltaMode?1:.002)*(t.ctrlKey?10:1)}function cs(){return navigator.maxTouchPoints||\"ontouchstart\"in this}function ps(t,e,n){var r=t.invertX(e[0][0])-n[0][0],i=t.invertX(e[1][0])-n[1][0],o=t.invertY(e[0][1])-n[0][1],a=t.invertY(e[1][1])-n[1][1];return t.translate(i>r?(r+i)/2:Math.min(0,r)||Math.max(0,i),a>o?(o+a)/2:Math.min(0,o)||Math.max(0,a))}function fn(){var t=ls,e=us,n=ps,r=fs,i=cs,o=[0,1/0],a=[[-1/0,-1/0],[1/0,1/0]],s=250,u=sn,l=dt(\"start\",\"zoom\",\"end\"),f,x,p,h=500,_=150,g=0,y=10;function d(c){c.property(\"__zoom\",zi).on(\"wheel.zoom\",$,{passive:!1}).on(\"mousedown.zoom\",L).on(\"dblclick.zoom\",O).filter(i).on(\"touchstart.zoom\",F).on(\"touchmove.zoom\",rt).on(\"touchend.zoom touchcancel.zoom\",Z).style(\"-webkit-tap-highlight-color\",\"rgba(0,0,0,0)\")}d.transform=function(c,k,w,N){var C=c.selection?c.selection():c;C.property(\"__zoom\",zi),c!==C?E(c,k,w,N):C.interrupt().each(function(){T(this,arguments).event(N).start().zoom(null,typeof k==\"function\"?k.apply(this,arguments):k).end()})},d.scaleBy=function(c,k,w,N){d.scaleTo(c,function(){var C=this.__zoom.k,z=typeof k==\"function\"?k.apply(this,arguments):k;return C*z},w,N)},d.scaleTo=function(c,k,w,N){d.transform(c,function(){var C=e.apply(this,arguments),z=this.__zoom,S=w==null?m(C):typeof w==\"function\"?w.apply(this,arguments):w,B=z.invert(S),P=typeof k==\"function\"?k.apply(this,arguments):k;return n(A(b(z,P),S,B),C,a)},w,N)},d.translateBy=function(c,k,w,N){d.transform(c,function(){return n(this.__zoom.translate(typeof k==\"function\"?k.apply(this,arguments):k,typeof w==\"function\"?w.apply(this,arguments):w),e.apply(this,arguments),a)},null,N)},d.translateTo=function(c,k,w,N,C){d.transform(c,function(){var z=e.apply(this,arguments),S=this.__zoom,B=N==null?m(z):typeof N==\"function\"?N.apply(this,arguments):N;return n(Nt.translate(B[0],B[1]).scale(S.k).translate(typeof k==\"function\"?-k.apply(this,arguments):-k,typeof w==\"function\"?-w.apply(this,arguments):-w),z,a)},N,C)};function b(c,k){return k=Math.max(o[0],Math.min(o[1],k)),k===c.k?c:new ft(k,c.x,c.y)}function A(c,k,w){var N=k[0]-w[0]*c.k,C=k[1]-w[1]*c.k;return N===c.x&&C===c.y?c:new ft(c.k,N,C)}function m(c){return[(+c[0][0]+ +c[1][0])/2,(+c[0][1]+ +c[1][1])/2]}function E(c,k,w,N){c.on(\"start.zoom\",function(){T(this,arguments).event(N).start()}).on(\"interrupt.zoom end.zoom\",function(){T(this,arguments).event(N).end()}).tween(\"zoom\",function(){var C=this,z=arguments,S=T(C,z).event(N),B=e.apply(C,z),P=w==null?m(B):typeof w==\"function\"?w.apply(C,z):w,q=Math.max(B[1][0]-B[0][0],B[1][1]-B[0][1]),G=C.__zoom,U=typeof k==\"function\"?k.apply(C,z):k,j=u(G.invert(P).concat(q/G.k),U.invert(P).concat(q/U.k));return function(Q){if(Q===1)Q=U;else{var W=j(Q),st=q/W[2];Q=new ft(st,P[0]-W[0]*st,P[1]-W[1]*st)}S.zoom(null,Q)}})}function T(c,k,w){return!w&&c.__zooming||new I(c,k)}function I(c,k){this.that=c,this.args=k,this.active=0,this.sourceEvent=null,this.extent=e.apply(c,k),this.taps=0}I.prototype={event:function(c){return c&&(this.sourceEvent=c),this},start:function(){return++this.active===1&&(this.that.__zooming=this,this.emit(\"start\")),this},zoom:function(c,k){return this.mouse&&c!==\"mouse\"&&(this.mouse[1]=k.invert(this.mouse[0])),this.touch0&&c!==\"touch\"&&(this.touch0[1]=k.invert(this.touch0[0])),this.touch1&&c!==\"touch\"&&(this.touch1[1]=k.invert(this.touch1[0])),this.that.__zoom=k,this.emit(\"zoom\"),this},end:function(){return--this.active===0&&(delete this.that.__zooming,this.emit(\"end\")),this},emit:function(c){var k=V(this.that).datum();l.call(c,this.that,new ln(c,{sourceEvent:this.sourceEvent,target:d,type:c,transform:this.that.__zoom,dispatch:l}),k)}};function $(c,...k){if(!t.apply(this,arguments))return;var w=T(this,k).event(c),N=this.__zoom,C=Math.max(o[0],Math.min(o[1],N.k*Math.pow(2,r.apply(this,arguments)))),z=ot(c);if(w.wheel)(w.mouse[0][0]!==z[0]||w.mouse[0][1]!==z[1])&&(w.mouse[1]=N.invert(w.mouse[0]=z)),clearTimeout(w.wheel);else{if(N.k===C)return;w.mouse=[z,N.invert(z)],zt(this),w.start()}Vt(c),w.wheel=setTimeout(S,_),w.zoom(\"mouse\",n(A(b(N,C),w.mouse[0],w.mouse[1]),w.extent,a));function S(){w.wheel=null,w.end()}}function L(c,...k){if(p||!t.apply(this,arguments))return;var w=c.currentTarget,N=T(this,k,!0).event(c),C=V(c.view).on(\"mousemove.zoom\",P,!0).on(\"mouseup.zoom\",q,!0),z=ot(c,w),S=c.clientX,B=c.clientY;Wt(c.view),Be(c),N.mouse=[z,this.__zoom.invert(z)],zt(this),N.start();function P(G){if(Vt(G),!N.moved){var U=G.clientX-S,j=G.clientY-B;N.moved=U*U+j*j>g}N.event(G).zoom(\"mouse\",n(A(N.that.__zoom,N.mouse[0]=ot(G,w),N.mouse[1]),N.extent,a))}function q(G){C.on(\"mousemove.zoom mouseup.zoom\",null),Jt(G.view,N.moved),Vt(G),N.event(G).end()}}function O(c,...k){if(t.apply(this,arguments)){var w=this.__zoom,N=ot(c.changedTouches?c.changedTouches[0]:c,this),C=w.invert(N),z=w.k*(c.shiftKey?.5:2),S=n(A(b(w,z),N,C),e.apply(this,k),a);Vt(c),s>0?V(this).transition().duration(s).call(E,S,N,c):V(this).call(d.transform,S,N,c)}}function F(c,...k){if(t.apply(this,arguments)){var w=c.touches,N=w.length,C=T(this,k,c.changedTouches.length===N).event(c),z,S,B,P;for(Be(c),S=0;S<N;++S)B=w[S],P=ot(B,this),P=[P,this.__zoom.invert(P),B.identifier],C.touch0?!C.touch1&&C.touch0[2]!==P[2]&&(C.touch1=P,C.taps=0):(C.touch0=P,z=!0,C.taps=1+!!f);f&&(f=clearTimeout(f)),z&&(C.taps<2&&(x=P[0],f=setTimeout(function(){f=null},h)),zt(this),C.start())}}function rt(c,...k){if(this.__zooming){var w=T(this,k).event(c),N=c.changedTouches,C=N.length,z,S,B,P;for(Vt(c),z=0;z<C;++z)S=N[z],B=ot(S,this),w.touch0&&w.touch0[2]===S.identifier?w.touch0[0]=B:w.touch1&&w.touch1[2]===S.identifier&&(w.touch1[0]=B);if(S=w.that.__zoom,w.touch1){var q=w.touch0[0],G=w.touch0[1],U=w.touch1[0],j=w.touch1[1],Q=(Q=U[0]-q[0])*Q+(Q=U[1]-q[1])*Q,W=(W=j[0]-G[0])*W+(W=j[1]-G[1])*W;S=b(S,Math.sqrt(Q/W)),B=[(q[0]+U[0])/2,(q[1]+U[1])/2],P=[(G[0]+j[0])/2,(G[1]+j[1])/2]}else if(w.touch0)B=w.touch0[0],P=w.touch0[1];else return;w.zoom(\"touch\",n(A(S,B,P),w.extent,a))}}function Z(c,...k){if(this.__zooming){var w=T(this,k).event(c),N=c.changedTouches,C=N.length,z,S;for(Be(c),p&&clearTimeout(p),p=setTimeout(function(){p=null},h),z=0;z<C;++z)S=N[z],w.touch0&&w.touch0[2]===S.identifier?delete w.touch0:w.touch1&&w.touch1[2]===S.identifier&&delete w.touch1;if(w.touch1&&!w.touch0&&(w.touch0=w.touch1,delete w.touch1),w.touch0)w.touch0[1]=this.__zoom.invert(w.touch0[0]);else if(w.end(),w.taps===2&&(S=ot(S,this),Math.hypot(x[0]-S[0],x[1]-S[1])<y)){var B=V(this).on(\"dblclick.zoom\");B&&B.apply(this,arguments)}}}return d.wheelDelta=function(c){return arguments.length?(r=typeof c==\"function\"?c:ae(+c),d):r},d.filter=function(c){return arguments.length?(t=typeof c==\"function\"?c:ae(!!c),d):t},d.touchable=function(c){return arguments.length?(i=typeof c==\"function\"?c:ae(!!c),d):i},d.extent=function(c){return arguments.length?(e=typeof c==\"function\"?c:ae([[+c[0][0],+c[0][1]],[+c[1][0],+c[1][1]]]),d):e},d.scaleExtent=function(c){return arguments.length?(o[0]=+c[0],o[1]=+c[1],d):[o[0],o[1]]},d.translateExtent=function(c){return arguments.length?(a[0][0]=+c[0][0],a[1][0]=+c[1][0],a[0][1]=+c[0][1],a[1][1]=+c[1][1],d):[[a[0][0],a[0][1]],[a[1][0],a[1][1]]]},d.constrain=function(c){return arguments.length?(n=c,d):n},d.duration=function(c){return arguments.length?(s=+c,d):s},d.interpolate=function(c){return arguments.length?(u=c,d):u},d.on=function(){var c=l.on.apply(l,arguments);return c===l?d:c},d.clickDistance=function(c){return arguments.length?(g=(c=+c)*c,d):Math.sqrt(g)},d.tapDistance=function(c){return arguments.length?(y=+c,d):y},d}var Re={primary_partner:{color:\"#f1c40f\",width:4,dashArray:\"\",label:\"Primary Partners\"},partner:{color:\"#ff6b9d\",width:3,dashArray:\"\",label:\"Partners\"},nesting_partner:{color:\"#ff4757\",width:3,dashArray:\"\",label:\"Nesting Partners\",double:!0},anchor_partner:{color:\"#c0392b\",width:4,dashArray:\"10,4\",label:\"Anchor Partners\"},fwb:{color:\"#a55eea\",width:2.5,dashArray:\"8,4\",label:\"Friends with Benefits\"},casual:{color:\"#45aaf2\",width:2,dashArray:\"3,5\",label:\"Casual\"},queerplatonic:{color:\"#26de81\",width:2,dashArray:\"12,3,3,3\",label:\"Queerplatonic\"},comet:{color:\"#a5b1c2\",width:1.5,dashArray:\"18,8\",label:\"Comet\"},friend:{color:\"#4fc3f7\",width:1.5,dashArray:\"\",label:\"Friends\"},metamour:{color:\"#546e7a\",width:1,dashArray:\"4,6\",label:\"Metamour\"},tbd:{color:\"#78909c\",width:1.5,dashArray:\"6,3,2,3\",label:\"TBD\"}},$i=[\"#e91e8c\",\"#9c27b0\",\"#3f51b5\",\"#2196f3\",\"#009688\",\"#4caf50\",\"#ff9800\",\"#f44336\",\"#00bcd4\",\"#8bc34a\"];function cn(t,e){if(e)return e;let n=0;for(let r=0;r<t.length;r++)n=(n<<5)-n+t.charCodeAt(r),n|=0;return $i[Math.abs(n)%$i.length]}function pn(t){return t.split(/\\s+/).slice(0,2).map(e=>{var n,r;return(r=(n=e[0])==null?void 0:n.toUpperCase())!=null?r:\"\"}).join(\"\")}var hn=28,hs=16;function Oi(t){return{bg:t?\"#0d1117\":\"#f0f4f8\",grid:t?\"rgba(255,255,255,0.025)\":\"rgba(0,0,0,0.04)\",text:t?\"#e6edf3\":\"#1c1e21\",textMuted:t?\"#8b949e\":\"#65676b\",nodeLabelBg:t?\"rgba(0,0,0,0.55)\":\"rgba(255,255,255,0.75)\",edgeLabelBg:t?\"rgba(13,17,23,0.75)\":\"rgba(240,244,248,0.8)\",panelBg:t?\"rgba(22,27,34,0.95)\":\"rgba(255,255,255,0.97)\",legendBg:t?\"rgba(13,17,23,0.45)\":\"rgba(255,255,255,0.5)\",panelBorder:t?\"rgba(48,54,61,0.9)\":\"rgba(208,215,222,0.9)\",panelText:t?\"#e6edf3\":\"#1c1e21\",panelMuted:t?\"#8b949e\":\"#65676b\",btnBg:t?\"rgba(33,38,45,0.9)\":\"rgba(255,255,255,0.9)\",btnBorder:t?\"rgba(48,54,61,0.8)\":\"rgba(208,215,222,0.8)\",btnText:t?\"#8b949e\":\"#65676b\"}}function ms(){if(document.getElementById(\"polymap-styles\"))return;let t=document.createElement(\"style\");t.id=\"polymap-styles\",t.textContent=`\n .polymap-wrap { position: relative; width: 100%; height: 100%; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }\n .polymap-wrap svg { display: block; width: 100%; height: 100%; cursor: grab; }\n .polymap-wrap svg:active { cursor: grabbing; }\n .polymap-node { cursor: pointer; }\n .polymap-node:hover .pm-halo { opacity: 0.35 !important; }\n .polymap-node:hover .pm-ring { stroke-width: 3 !important; }\n\n .pm-info-panel {\n position: absolute;\n min-width: 200px;\n max-width: 280px;\n border-radius: 12px;\n padding: 14px 16px;\n box-shadow: 0 8px 32px rgba(0,0,0,0.35);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid;\n pointer-events: auto;\n z-index: 100;\n transition: opacity 0.15s ease;\n }\n .pm-info-panel.hidden { opacity: 0; pointer-events: none; }\n .pm-info-header { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }\n .pm-info-avatar {\n width: 40px; height: 40px; border-radius: 50%; flex-shrink: 0;\n display: flex; align-items: center; justify-content: center;\n font-size: 14px; font-weight: 700; color: #fff;\n background-size: cover; background-position: center;\n overflow: hidden;\n }\n .pm-info-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }\n .pm-info-name { font-size: 15px; font-weight: 600; line-height: 1.2; }\n .pm-info-pronouns { font-size: 12px; margin-top: 1px; }\n .pm-info-close {\n margin-left: auto; background: none; border: none; cursor: pointer;\n font-size: 18px; line-height: 1; padding: 0 2px; opacity: 0.5;\n transition: opacity 0.1s;\n }\n .pm-info-close:hover { opacity: 1; }\n .pm-info-links { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }\n .pm-info-link {\n font-size: 12px; padding: 3px 9px; border-radius: 20px;\n border: 1px solid; text-decoration: none; opacity: 0.85;\n transition: opacity 0.1s;\n }\n .pm-info-link:hover { opacity: 1; }\n\n .pm-controls {\n position: absolute; top: 12px; right: 12px;\n display: flex; flex-direction: column; gap: 6px; z-index: 50;\n }\n .pm-btn {\n border: 1px solid; border-radius: 8px; padding: 6px 12px;\n font-size: 12px; cursor: pointer; backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px); transition: opacity 0.1s;\n white-space: nowrap;\n }\n .pm-btn:hover { opacity: 0.8; }\n\n .pm-legend {\n position: absolute; bottom: 12px; left: 12px; z-index: 50;\n border: 1px solid; border-radius: 10px; padding: 10px 14px;\n backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);\n min-width: 160px;\n }\n .pm-legend.hidden { display: none; }\n .pm-legend-title { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 8px; }\n .pm-legend-item { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; font-size: 12px; }\n .pm-legend-line { flex-shrink: 0; }\n `,document.head.appendChild(t)}function Di(t,e){ms();let n=e.settings.theme!==\"light\",r=!0,i=!0,o=!0,a=Nt,s=document.createElement(\"div\");s.className=\"polymap-wrap\",t.appendChild(s);let u=new Map;e.people.forEach(v=>u.set(v.id,0)),e.relationships.forEach(v=>{var M,R;u.set(v.from,((M=u.get(v.from))!=null?M:0)+1),u.set(v.to,((R=u.get(v.to))!=null?R:0)+1)});let l=e.people.length,f=e.people.map((v,M)=>{var X;let R=M/l*2*Math.PI,D=220;return bn(_n({},v),{x:480+Math.cos(R)*D,y:360+Math.sin(R)*D,vx:0,vy:0,fx:null,fy:null,connectionCount:(X=u.get(v.id))!=null?X:0})}),x=new Map(f.map(v=>[v.id,v])),p=e.relationships.map(v=>({source:x.get(v.from),target:x.get(v.to),relationship:v}));function h(v){return e.settings.nodeScale===\"connections\"?hn+v.connectionCount*4:hn}let _=document.createElementNS(\"http://www.w3.org/2000/svg\",\"svg\");s.appendChild(_);let g=V(_),y=g.append(\"defs\");y.append(\"pattern\").attr(\"id\",\"pm-grid\").attr(\"width\",40).attr(\"height\",40).attr(\"patternUnits\",\"userSpaceOnUse\").append(\"path\").attr(\"d\",\"M 40 0 L 0 0 0 40\").attr(\"fill\",\"none\").attr(\"class\",\"pm-grid-path\").attr(\"stroke-width\",\"1\"),y.append(\"clipPath\").attr(\"id\",\"pm-node-clip\").append(\"circle\").attr(\"r\",hn);let b=y.append(\"filter\").attr(\"id\",\"pm-node-glow\").attr(\"x\",\"-60%\").attr(\"y\",\"-60%\").attr(\"width\",\"220%\").attr(\"height\",\"220%\");b.append(\"feGaussianBlur\").attr(\"in\",\"SourceGraphic\").attr(\"stdDeviation\",\"5\").attr(\"result\",\"blur\");let A=b.append(\"feMerge\");A.append(\"feMergeNode\").attr(\"in\",\"blur\"),A.append(\"feMergeNode\").attr(\"in\",\"SourceGraphic\");let m=y.append(\"filter\").attr(\"id\",\"pm-edge-glow\").attr(\"x\",\"-40%\").attr(\"y\",\"-40%\").attr(\"width\",\"180%\").attr(\"height\",\"180%\");m.append(\"feGaussianBlur\").attr(\"in\",\"SourceGraphic\").attr(\"stdDeviation\",\"2.5\").attr(\"result\",\"blur\");let E=m.append(\"feMerge\");E.append(\"feMergeNode\").attr(\"in\",\"blur\"),E.append(\"feMergeNode\").attr(\"in\",\"SourceGraphic\");let T=g.append(\"rect\").attr(\"class\",\"pm-bg\").attr(\"width\",\"100%\").attr(\"height\",\"100%\");g.append(\"rect\").attr(\"class\",\"pm-grid-rect\").attr(\"width\",\"100%\").attr(\"height\",\"100%\").attr(\"fill\",\"url(#pm-grid)\");let I=g.append(\"g\").attr(\"class\",\"pm-graph-root\"),$=I.append(\"g\").attr(\"class\",\"pm-edges\"),L=I.append(\"g\").attr(\"class\",\"pm-nodes\"),O=$.selectAll(\"g.pm-edge\").data(p).join(\"g\").attr(\"class\",\"pm-edge\"),F=O.append(\"line\").attr(\"class\",\"pm-edge-bg\").attr(\"stroke-linecap\",\"round\"),rt=O.append(\"line\").attr(\"class\",\"pm-edge-line\").attr(\"stroke-linecap\",\"round\"),Z=O.append(\"g\").attr(\"class\",\"pm-edge-label\");Z.append(\"rect\").attr(\"rx\",4).attr(\"ry\",4).attr(\"x\",-28).attr(\"y\",-8).attr(\"width\",56).attr(\"height\",14),Z.append(\"text\").attr(\"text-anchor\",\"middle\").attr(\"dominant-baseline\",\"central\").attr(\"y\",0).attr(\"font-size\",\"9\").attr(\"font-family\",'-apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif').attr(\"font-weight\",\"500\"),O.each(function(v){var ht;let M=Re[v.relationship.type],R=(ht=v.relationship.label)!=null?ht:M.label,D=Math.max(40,R.length*5.5+12);V(this).select(\"rect\").attr(\"x\",-D/2).attr(\"width\",D);let X=V(this),tt=X.select(\".pm-edge-bg\"),kt=X.select(\".pm-edge-line\"),Dt=X.select(\".pm-edge-label\").select(\"text\");M.double?tt.attr(\"stroke-width\",M.width*2.6).style(\"visibility\",\"visible\"):tt.style(\"visibility\",\"hidden\"),kt.attr(\"stroke\",M.color).attr(\"stroke-width\",M.width).attr(\"stroke-dasharray\",M.dashArray||null).attr(\"opacity\",\"0.85\").attr(\"filter\",\"url(#pm-edge-glow)\"),Dt.text(R).attr(\"fill\",M.color)});let c=L.selectAll(\"g.polymap-node\").data(f).join(\"g\").attr(\"class\",\"polymap-node\").attr(\"data-id\",v=>v.id);c.each(function(v){let M=h(v),R=cn(v.id,v.color),D=V(this);if(D.append(\"circle\").attr(\"class\",\"pm-halo\").attr(\"r\",M+10).attr(\"fill\",R).attr(\"opacity\",.15),v.photo){let tt=`pm-clip-${v.id}`;V(_).select(\"defs\").append(\"clipPath\").attr(\"id\",tt).append(\"circle\").attr(\"r\",M),D.append(\"image\").attr(\"href\",v.photo).attr(\"x\",-M).attr(\"y\",-M).attr(\"width\",M*2).attr(\"height\",M*2).attr(\"clip-path\",`url(#${tt})`).attr(\"preserveAspectRatio\",\"xMidYMid slice\")}else D.append(\"circle\").attr(\"r\",M).attr(\"fill\",R).attr(\"filter\",\"url(#pm-node-glow)\"),D.append(\"text\").attr(\"text-anchor\",\"middle\").attr(\"dominant-baseline\",\"central\").attr(\"font-size\",Math.round(M*.5)).attr(\"font-weight\",\"700\").attr(\"font-family\",'-apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif').attr(\"fill\",\"#ffffff\").attr(\"pointer-events\",\"none\").text(pn(v.name));D.append(\"circle\").attr(\"class\",\"pm-ring\").attr(\"r\",M).attr(\"fill\",\"none\").attr(\"stroke\",R).attr(\"stroke-width\",2).attr(\"opacity\",.9);let X=M+hs;D.append(\"rect\").attr(\"class\",\"pm-label-bg\").attr(\"rx\",4).attr(\"ry\",4).attr(\"x\",-36).attr(\"y\",X-9).attr(\"width\",72).attr(\"height\",15),D.append(\"text\").attr(\"class\",\"pm-label-text\").attr(\"text-anchor\",\"middle\").attr(\"y\",X).attr(\"font-size\",\"11\").attr(\"font-family\",'-apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif').attr(\"pointer-events\",\"none\").text(v.name)});let k=He(f).force(\"link\",Ve(p).id(v=>v.id).distance(170).strength(.5)).force(\"charge\",Xe().strength(-500)).force(\"center\",Le(480,360).strength(.08)).force(\"collision\",Ge(v=>h(v)+24)).on(\"tick\",w);function w(){O.each(function(v){let M=V(this),R=v.source.x,D=v.source.y,X=v.target.x,tt=v.target.y;M.select(\".pm-edge-bg\").attr(\"x1\",R).attr(\"y1\",D).attr(\"x2\",X).attr(\"y2\",tt),M.select(\".pm-edge-line\").attr(\"x1\",R).attr(\"y1\",D).attr(\"x2\",X).attr(\"y2\",tt),M.select(\".pm-edge-label\").attr(\"transform\",`translate(${(R+X)/2},${(D+tt)/2})`)}),c.attr(\"transform\",v=>`translate(${v.x},${v.y})`)}let N=null,C=Ke().on(\"start\",(v,M)=>{N&&N!==M&&(N.fx=null,N.fy=null),v.active||k.alphaTarget(.3).restart(),M.fx=M.x,M.fy=M.y,N=M}).on(\"drag\",(v,M)=>{M.fx=v.x,M.fy=v.y}).on(\"end\",v=>{v.active||k.alphaTarget(0)});c.call(C),c.on(\"dblclick\",(v,M)=>{v.stopPropagation(),M.fx=null,M.fy=null,N===M&&(N=null),k.alphaTarget(.1).restart()});let z=fn().scaleExtent([.05,8]).filter(v=>v.type!==\"dblclick\").on(\"zoom\",v=>{a=v.transform,I.attr(\"transform\",v.transform.toString())});g.call(z),g.on(\"dblclick.zoom\",null),g.on(\"click\",()=>P()),c.on(\"click\",(v,M)=>{v.stopPropagation(),B(M,v)});let S=document.createElement(\"div\");S.className=\"pm-info-panel hidden\",s.appendChild(S);function B(v,M){var mn,dn,gn;let R=cn(v.id,v.color),D=Oi(n);S.style.background=D.panelBg,S.style.borderColor=D.panelBorder,S.style.color=D.panelText;let X=v.photo?`<img src=\"${$t(v.photo)}\" alt=\"${$t(v.name)}\"/>`:`<span style=\"width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:${R}\">${$t(pn(v.name))}</span>`,tt=((mn=v.links)!=null?mn:[]).length>0?`<div class=\"pm-info-links\">${((dn=v.links)!=null?dn:[]).map(xn=>`<a class=\"pm-info-link\" href=\"${$t(xn.url)}\" target=\"_blank\" rel=\"noopener noreferrer\"\n style=\"color:${R};border-color:${R}22\">${$t(xn.label)}</a>`).join(\"\")}</div>`:\"\";S.innerHTML=`\n <div class=\"pm-info-header\">\n <div class=\"pm-info-avatar\" style=\"background:${R}\">${X}</div>\n <div>\n <div class=\"pm-info-name\" style=\"color:${D.panelText}\">${$t(v.name)}</div>\n ${v.pronouns?`<div class=\"pm-info-pronouns\" style=\"color:${D.panelMuted}\">${$t(v.pronouns)}</div>`:\"\"}\n </div>\n <button class=\"pm-info-close\" style=\"color:${D.panelText}\" aria-label=\"Close\">\\xD7</button>\n </div>\n ${tt}\n `,(gn=S.querySelector(\".pm-info-close\"))==null||gn.addEventListener(\"click\",P);let kt=s.getBoundingClientRect(),Ot=a.applyX(v.x),Dt=a.applyY(v.y),ht=240,le=120,Et=Ot+48,mt=Dt-40;Et+ht>kt.width-8&&(Et=Ot-ht-48),mt+le>kt.height-8&&(mt=kt.height-le-8),mt<8&&(mt=8),Et<8&&(Et=8),S.style.left=`${Et}px`,S.style.top=`${mt}px`,S.classList.remove(\"hidden\")}function P(){S.classList.add(\"hidden\")}let q=document.createElement(\"div\");q.className=\"pm-controls\",s.appendChild(q);let G=document.createElement(\"button\");G.className=\"pm-btn\",G.title=\"Toggle dark/light mode\",q.appendChild(G);let U=document.createElement(\"button\");U.className=\"pm-btn\",U.textContent=\"Legend\",U.title=\"Toggle legend\",q.appendChild(U);let j=document.createElement(\"button\");j.className=\"pm-btn\",j.title=\"Toggle edge labels\",q.appendChild(j);let Q=document.createElement(\"button\");Q.className=\"pm-btn\",Q.title=\"Toggle node names\",q.appendChild(Q);let W=document.createElement(\"button\");W.className=\"pm-btn\",W.textContent=\"\\u22A1 Fit\",W.title=\"Fit graph to view\",q.appendChild(W),W.addEventListener(\"click\",()=>{let v=_.getBoundingClientRect();if(!v.width)return;let M=f.map(mt=>mt.x),R=f.map(mt=>mt.y),D=Math.min(...M)-80,X=Math.min(...R)-80,tt=Math.max(...M)+80,kt=Math.max(...R)+80,Ot=tt-D,Dt=kt-X,ht=Math.min(.9,Math.min(v.width/Ot,v.height/Dt)),le=v.width/2-ht*(D+Ot/2),Et=v.height/2-ht*(X+Dt/2);g.transition().duration(500).call(z.transform,Nt.translate(le,Et).scale(ht))});let st=document.createElement(\"div\");st.className=\"pm-legend\",s.appendChild(st);let Bi=[...new Set(e.relationships.map(v=>v.type))];function Ri(v){st.style.background=v.legendBg,st.style.borderColor=v.panelBorder,st.style.color=v.panelText;let M=\"http://www.w3.org/2000/svg\";st.innerHTML=`<div class=\"pm-legend-title\" style=\"color:${v.panelMuted}\">Relationships</div>`+Bi.map(R=>{let D=Re[R],X=D.dashArray?`stroke-dasharray=\"${D.dashArray}\"`:\"\",tt=`<svg class=\"pm-legend-line\" width=\"32\" height=\"12\" viewBox=\"0 0 32 12\">\n ${D.double?`<line x1=\"0\" y1=\"6\" x2=\"32\" y2=\"6\" stroke=\"${v.panelBg}\" stroke-width=\"${D.width*2.6}\" stroke-linecap=\"round\"/>\n <line x1=\"0\" y1=\"6\" x2=\"32\" y2=\"6\" stroke=\"${D.color}\" stroke-width=\"${D.width}\" stroke-linecap=\"round\" ${X}/>`:`<line x1=\"0\" y1=\"6\" x2=\"32\" y2=\"6\" stroke=\"${D.color}\" stroke-width=\"${D.width}\" stroke-linecap=\"round\" ${X}/>`}\n </svg>`;return`<div class=\"pm-legend-item\" style=\"color:${v.panelText}\">${tt}<span>${D.label}</span></div>`}).join(\"\")}U.addEventListener(\"click\",()=>{r=!r,st.classList.toggle(\"hidden\",!r)}),j.addEventListener(\"click\",()=>{i=!i,O.selectAll(\".pm-edge-label\").style(\"display\",i?null:\"none\"),se()}),Q.addEventListener(\"click\",()=>{o=!o,c.selectAll(\".pm-label-bg, .pm-label-text\").style(\"display\",o?null:\"none\"),se()});function se(){let v=Oi(n);T.attr(\"fill\",v.bg),g.selectAll(\".pm-grid-path\").attr(\"stroke\",v.grid),O.each(function(M){let R=Re[M.relationship.type];V(this).select(\".pm-edge-label rect\").attr(\"fill\",v.edgeLabelBg),R.double&&V(this).select(\".pm-edge-bg\").attr(\"stroke\",v.bg)}),c.each(function(){V(this).select(\".pm-label-bg\").attr(\"fill\",v.nodeLabelBg),V(this).select(\".pm-label-text\").attr(\"fill\",v.text)}),[G,U,j,Q,W].forEach(M=>{M.style.background=v.btnBg,M.style.borderColor=v.btnBorder,M.style.color=v.btnText}),G.textContent=n?\"\\u2600 Light\":\"\\u263E Dark\",j.textContent=i?\"Labels On\":\"Labels Off\",Q.textContent=o?\"Names On\":\"Names Off\",Ri(v)}G.addEventListener(\"click\",()=>{n=!n,se()}),se(),setTimeout(()=>W.click(),600)}function $t(t){return t.replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/\"/g,\"&quot;\")}if(typeof window!=\"undefined\"){let t=function(){let e=window.__POLYMAP_DATA__,n=document.getElementById(\"polymap-root\");e&&n&&Di(n,e)};ds=t,document.readyState===\"loading\"?document.addEventListener(\"DOMContentLoaded\",t):t()}var ds;return Yi(gs);})();\n";
+74
src/parser.ts
··· 1 + import { load } from 'js-yaml'; 2 + import { readFileSync } from 'fs'; 3 + import type { PolyculeData, Settings, Person, Relationship, RelationshipType } from './types.js'; 4 + 5 + const VALID_TYPES = new Set<RelationshipType>([ 6 + 'primary_partner', 'partner', 'nesting_partner', 'anchor_partner', 'fwb', 'casual', 7 + 'queerplatonic', 'comet', 'friend', 'metamour', 'tbd', 8 + ]); 9 + 10 + export function parseConfig(filePath: string): PolyculeData { 11 + let raw: string; 12 + try { 13 + raw = readFileSync(filePath, 'utf8'); 14 + } catch { 15 + throw new Error(`Cannot read file: ${filePath}`); 16 + } 17 + 18 + const data = load(raw) as Record<string, unknown>; 19 + if (!data || typeof data !== 'object') throw new Error('Config must be a YAML object'); 20 + if (!Array.isArray(data['people'])) throw new Error('"people" must be an array'); 21 + if (!Array.isArray(data['relationships'])) throw new Error('"relationships" must be an array'); 22 + 23 + const s = data['settings'] as Record<string, unknown> | undefined; 24 + const settings: Settings = { 25 + theme: s?.['theme'] === 'light' ? 'light' : 'dark', 26 + nodeScale: s?.['nodeScale'] === 'connections' ? 'connections' : 'uniform', 27 + }; 28 + 29 + const people: Person[] = (data['people'] as unknown[]).map((p, i) => { 30 + const person = p as Record<string, unknown>; 31 + if (!person?.['id']) throw new Error(`Person[${i}] missing "id"`); 32 + if (!person?.['name']) throw new Error(`Person "${person['id']}" missing "name"`); 33 + return { 34 + id: String(person['id']), 35 + name: String(person['name']), 36 + pronouns: person['pronouns'] != null ? String(person['pronouns']) : undefined, 37 + photo: person['photo'] != null ? String(person['photo']) : undefined, 38 + color: person['color'] != null ? String(person['color']) : undefined, 39 + links: Array.isArray(person['links']) 40 + ? (person['links'] as Record<string, unknown>[]).map(l => ({ 41 + label: String(l['label']), 42 + url: String(l['url']), 43 + })) 44 + : [], 45 + }; 46 + }); 47 + 48 + const ids = new Set(people.map(p => p.id)); 49 + 50 + const relationships: Relationship[] = (data['relationships'] as unknown[]).map((r, i) => { 51 + const rel = r as Record<string, unknown>; 52 + if (!rel?.['from']) throw new Error(`Relationship[${i}] missing "from"`); 53 + if (!rel?.['to']) throw new Error(`Relationship[${i}] missing "to"`); 54 + if (!rel?.['type']) throw new Error(`Relationship[${i}] missing "type"`); 55 + const type = rel['type'] as string; 56 + if (!VALID_TYPES.has(type as RelationshipType)) { 57 + throw new Error( 58 + `Relationship[${i}] invalid type "${type}". Valid: ${[...VALID_TYPES].join(', ')}` 59 + ); 60 + } 61 + const from = String(rel['from']); 62 + const to = String(rel['to']); 63 + if (!ids.has(from)) throw new Error(`Unknown person "${from}"`); 64 + if (!ids.has(to)) throw new Error(`Unknown person "${to}"`); 65 + return { 66 + from, 67 + to, 68 + type: type as RelationshipType, 69 + label: rel['label'] != null ? String(rel['label']) : undefined, 70 + }; 71 + }); 72 + 73 + return { settings, people, relationships }; 74 + }
+81
src/simulate.ts
··· 1 + import { 2 + forceSimulation, 3 + forceLink, 4 + forceManyBody, 5 + forceCenter, 6 + forceCollide, 7 + type SimulationNodeDatum, 8 + type SimulationLinkDatum, 9 + } from 'd3-force'; 10 + import type { PolyculeData, Person, Relationship } from './types.js'; 11 + 12 + export interface SimNode extends SimulationNodeDatum, Person { 13 + x: number; 14 + y: number; 15 + connectionCount: number; 16 + } 17 + 18 + export interface SimLink extends SimulationLinkDatum<SimNode> { 19 + source: SimNode; 20 + target: SimNode; 21 + relationship: Relationship; 22 + } 23 + 24 + const WIDTH = 960; 25 + const HEIGHT = 720; 26 + export const NODE_RADIUS = 28; 27 + 28 + export function simulate(data: PolyculeData): { nodes: SimNode[]; links: SimLink[] } { 29 + const count = data.people.length; 30 + 31 + // Pre-compute connection counts for node scaling 32 + const connCount = new Map<string, number>(); 33 + data.people.forEach(p => connCount.set(p.id, 0)); 34 + data.relationships.forEach(r => { 35 + connCount.set(r.from, (connCount.get(r.from) ?? 0) + 1); 36 + connCount.set(r.to, (connCount.get(r.to) ?? 0) + 1); 37 + }); 38 + 39 + // Deterministic circle layout as starting positions 40 + const nodes: SimNode[] = data.people.map((p, i) => { 41 + const angle = (i / count) * 2 * Math.PI; 42 + return { 43 + ...p, 44 + x: WIDTH / 2 + Math.cos(angle) * 220, 45 + y: HEIGHT / 2 + Math.sin(angle) * 220, 46 + connectionCount: connCount.get(p.id) ?? 0, 47 + }; 48 + }); 49 + 50 + const nodeById = new Map<string, SimNode>(nodes.map(n => [n.id, n])); 51 + 52 + const links: SimLink[] = data.relationships.map(r => ({ 53 + source: nodeById.get(r.from)!, 54 + target: nodeById.get(r.to)!, 55 + relationship: r, 56 + })); 57 + 58 + // Scale forces with graph size so denser graphs spread out properly 59 + const chargeStrength = -Math.max(800, count * 140); 60 + const linkDistance = Math.max(180, 140 + count * 10); 61 + 62 + const sim = forceSimulation<SimNode>(nodes) 63 + .force( 64 + 'link', 65 + forceLink<SimNode, SimLink>(links) 66 + .id(d => d.id) 67 + .distance(linkDistance) 68 + .strength(0.35) 69 + ) 70 + .force('charge', forceManyBody<SimNode>().strength(chargeStrength)) 71 + .force('center', forceCenter(WIDTH / 2, HEIGHT / 2).strength(0.06)) 72 + .force('collision', forceCollide<SimNode>(NODE_RADIUS + 40)) 73 + // Lower alphaDecay → more ticks before cooling, gives layout more time to settle 74 + .alphaDecay(0.015) 75 + .stop(); 76 + 77 + const iterations = Math.ceil(Math.log(sim.alphaMin()) / Math.log(1 - sim.alphaDecay())); 78 + for (let i = 0; i < iterations; ++i) sim.tick(); 79 + 80 + return { nodes, links }; 81 + }
+102
src/styles.ts
··· 1 + import type { RelationshipType } from './types.js'; 2 + 3 + export interface RelationshipStyle { 4 + color: string; 5 + width: number; 6 + dashArray: string; 7 + label: string; 8 + double?: boolean; 9 + } 10 + 11 + export const RELATIONSHIP_STYLES: Record<RelationshipType, RelationshipStyle> = { 12 + primary_partner: { 13 + color: '#f1c40f', 14 + width: 4, 15 + dashArray: '', 16 + label: 'Primary Partners', 17 + }, 18 + partner: { 19 + color: '#ff6b9d', 20 + width: 3, 21 + dashArray: '', 22 + label: 'Partners', 23 + }, 24 + nesting_partner: { 25 + color: '#ff4757', 26 + width: 3, 27 + dashArray: '', 28 + label: 'Nesting Partners', 29 + double: true, 30 + }, 31 + anchor_partner: { 32 + color: '#c0392b', 33 + width: 4, 34 + dashArray: '10,4', 35 + label: 'Anchor Partners', 36 + }, 37 + fwb: { 38 + color: '#a55eea', 39 + width: 2.5, 40 + dashArray: '8,4', 41 + label: 'Friends with Benefits', 42 + }, 43 + casual: { 44 + color: '#45aaf2', 45 + width: 2, 46 + dashArray: '3,5', 47 + label: 'Casual', 48 + }, 49 + queerplatonic: { 50 + color: '#26de81', 51 + width: 2, 52 + dashArray: '12,3,3,3', 53 + label: 'Queerplatonic', 54 + }, 55 + comet: { 56 + color: '#a5b1c2', 57 + width: 1.5, 58 + dashArray: '18,8', 59 + label: 'Comet', 60 + }, 61 + friend: { 62 + color: '#4fc3f7', 63 + width: 1.5, 64 + dashArray: '', 65 + label: 'Friends', 66 + }, 67 + metamour: { 68 + color: '#546e7a', 69 + width: 1, 70 + dashArray: '4,6', 71 + label: 'Metamour', 72 + }, 73 + tbd: { 74 + color: '#78909c', 75 + width: 1.5, 76 + dashArray: '6,3,2,3', 77 + label: 'TBD', 78 + }, 79 + }; 80 + 81 + const PALETTE = [ 82 + '#e91e8c', '#9c27b0', '#3f51b5', '#2196f3', '#009688', 83 + '#4caf50', '#ff9800', '#f44336', '#00bcd4', '#8bc34a', 84 + ]; 85 + 86 + export function nodeColor(id: string, explicit?: string): string { 87 + if (explicit) return explicit; 88 + let h = 0; 89 + for (let i = 0; i < id.length; i++) { 90 + h = (h << 5) - h + id.charCodeAt(i); 91 + h |= 0; 92 + } 93 + return PALETTE[Math.abs(h) % PALETTE.length]; 94 + } 95 + 96 + export function initials(name: string): string { 97 + return name 98 + .split(/\s+/) 99 + .slice(0, 2) 100 + .map(w => w[0]?.toUpperCase() ?? '') 101 + .join(''); 102 + }
+44
src/types.ts
··· 1 + export type RelationshipType = 2 + | 'primary_partner' 3 + | 'partner' 4 + | 'nesting_partner' 5 + | 'anchor_partner' 6 + | 'fwb' 7 + | 'casual' 8 + | 'queerplatonic' 9 + | 'comet' 10 + | 'friend' 11 + | 'metamour' 12 + | 'tbd'; 13 + 14 + export interface PersonLink { 15 + label: string; 16 + url: string; 17 + } 18 + 19 + export interface Person { 20 + id: string; 21 + name: string; 22 + pronouns?: string; 23 + photo?: string; 24 + color?: string; 25 + links?: PersonLink[]; 26 + } 27 + 28 + export interface Relationship { 29 + from: string; 30 + to: string; 31 + type: RelationshipType; 32 + label?: string; 33 + } 34 + 35 + export interface Settings { 36 + theme: 'dark' | 'light'; 37 + nodeScale: 'uniform' | 'connections'; 38 + } 39 + 40 + export interface PolyculeData { 41 + settings: Settings; 42 + people: Person[]; 43 + relationships: Relationship[]; 44 + }
+19
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2022", 4 + "module": "NodeNext", 5 + "moduleResolution": "NodeNext", 6 + "lib": ["ES2022", "DOM"], 7 + "strict": true, 8 + "esModuleInterop": true, 9 + "skipLibCheck": true, 10 + "outDir": "dist", 11 + "rootDir": "src", 12 + "resolveJsonModule": true, 13 + "declaration": true, 14 + "declarationMap": true, 15 + "sourceMap": true 16 + }, 17 + "include": ["src/**/*"], 18 + "exclude": ["node_modules", "dist"] 19 + }