tangled vouch map with historical data
7
fork

Configure Feed

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

feat: add cosmo rendering

+2957 -844
+2 -2
.air.toml
··· 7 7 bin = "./tmp/main" 8 8 cmd = "go build -o ./tmp/main ./cmd/ingest" 9 9 delay = 1000 10 - exclude_dir = ["assets", "tmp", "vendor", "testdata", "web"] 10 + exclude_dir = ["assets", "tmp", "vendor", "testdata", "web", "node_modules", "web-dist"] 11 11 exclude_regex = ["_test.go"] 12 12 exclude_unchanged = true 13 13 follow_symlink = true 14 14 full_bin = "" 15 15 include_dir = [] 16 - include_ext = ["go", "html", "js", "css"] 16 + include_ext = ["go"] 17 17 include_file = [] 18 18 kill_delay = "0s" 19 19 log = "build-errors.log"
+2
.gitignore
··· 5 5 tangle-backfill 6 6 tangle-resolve 7 7 tmp/ 8 + node_modules/ 9 + web-dist/
+16 -24
internal/web/server.go
··· 8 8 "log/slog" 9 9 "net/http" 10 10 "net/url" 11 - "os" 12 11 "strings" 13 12 "time" 14 13 ··· 69 68 s.mux.HandleFunc("/api/stats", s.handleStats) 70 69 s.mux.HandleFunc("/api/resolve", s.handleResolve) 71 70 s.mux.HandleFunc("/api/profiles", s.handleProfiles) 72 - s.mux.HandleFunc("/", s.handleIndex) 71 + // serve Vite-built static assets 72 + s.mux.Handle("/", http.FileServer(http.Dir("web-dist"))) 73 73 } 74 74 75 75 func (s *Server) Serve(addr string) error { ··· 236 236 }) 237 237 } 238 238 239 - func (s *Server) handleTimeRange(w http.ResponseWriter, r *http.Request) { 240 - min, max, err := s.store.TimeRange() 241 - if err != nil { 242 - http.Error(w, err.Error(), http.StatusInternalServerError) 243 - return 244 - } 245 - writeJSON(w, map[string]string{ 246 - "min": min.Format(time.RFC3339), 247 - "max": max.Format(time.RFC3339), 248 - }) 249 - } 239 + func writeGraphJSON(w http.ResponseWriter, vouches []db.Vouch, follows []db.Follow, store *db.Store) { 240 + profiles, _ := store.AllProfiles() 250 241 251 - func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) { 252 - data, err := os.ReadFile("web/index.html") 253 - if err != nil { 254 - http.Error(w, "ui not found", http.StatusNotFound) 255 - return 242 + // Build set of tombstoned DIDs (handle="!") to exclude 243 + tombstoned := make(map[string]bool) 244 + for did, p := range profiles { 245 + if p.Handle == "!" { 246 + tombstoned[did] = true 247 + } 256 248 } 257 - w.Header().Set("Content-Type", "text/html; charset=utf-8") 258 - w.Write(data) 259 - } 260 249 261 - func writeGraphJSON(w http.ResponseWriter, vouches []db.Vouch, follows []db.Follow, store *db.Store) { 262 250 nodeSet := make(map[string]bool) 263 251 var edges []GraphEdge 264 252 265 253 for _, v := range vouches { 254 + if tombstoned[v.VoucherDID] || tombstoned[v.VoucheeDID] { 255 + continue 256 + } 266 257 nodeSet[v.VoucherDID] = true 267 258 nodeSet[v.VoucheeDID] = true 268 259 edges = append(edges, GraphEdge{ ··· 275 266 } 276 267 277 268 for _, f := range follows { 269 + if tombstoned[f.ActorDID] || tombstoned[f.SubjectDID] { 270 + continue 271 + } 278 272 nodeSet[f.ActorDID] = true 279 273 nodeSet[f.SubjectDID] = true 280 274 edges = append(edges, GraphEdge{ ··· 284 278 Time: f.UpdatedAt.Format(time.RFC3339), 285 279 }) 286 280 } 287 - 288 - profiles, _ := store.AllProfiles() 289 281 290 282 nodes := make([]GraphNode, 0, len(nodeSet)) 291 283 for id := range nodeSet {
+2121
package-lock.json
··· 1 + { 2 + "name": "tangle-of-trust", 3 + "version": "1.0.0", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "tangle-of-trust", 9 + "version": "1.0.0", 10 + "license": "ISC", 11 + "dependencies": { 12 + "@cosmograph/cosmograph": "^2.3.1" 13 + }, 14 + "devDependencies": { 15 + "vite": "^8.0.10" 16 + } 17 + }, 18 + "node_modules/@cosmograph/cosmograph": { 19 + "version": "2.3.1", 20 + "resolved": "https://registry.npmjs.org/@cosmograph/cosmograph/-/cosmograph-2.3.1.tgz", 21 + "integrity": "sha512-FsaNg2zNccczMG2azPsVRh7jIUtEimc+KvIEYENJTQRDJeABRJkHPnAxXHNz04nOQSiDM4dHbQ1e4ZjVOAML/w==", 22 + "license": "CC-BY-NC-4.0", 23 + "dependencies": { 24 + "@cosmograph/ui": "2.3.1", 25 + "@cosmos.gl/graph": "3.0.0-beta.8", 26 + "@duckdb/duckdb-wasm": "1.32.0", 27 + "@interacta/css-labels": "^0.6.0", 28 + "@supabase/supabase-js": "^2.98.0", 29 + "@uwdata/mosaic-core": "^0.21.1", 30 + "@uwdata/mosaic-plot": "^0.21.1", 31 + "@uwdata/mosaic-sql": "^0.21.1", 32 + "@uwdata/vgplot": "^0.21.1", 33 + "apache-arrow": "17.0.0", 34 + "d3-array": "^3.2.4", 35 + "d3-brush": "^3.0.0", 36 + "d3-color": "^3.1.0", 37 + "d3-interpolate": "^3.0.1", 38 + "d3-scale": "^4.0.2", 39 + "d3-selection": "^3.0.0", 40 + "dompurify": "^3.2.6" 41 + } 42 + }, 43 + "node_modules/@cosmograph/ui": { 44 + "version": "2.3.1", 45 + "resolved": "https://registry.npmjs.org/@cosmograph/ui/-/ui-2.3.1.tgz", 46 + "integrity": "sha512-uhR2hd3O8WOLaH1diw5eo1vRbknm/rZ6Rm1orpYy1H6ZrdG7eGRDb7Yo+QnOxCX0MaH+yBSE8n3X110rKhmtdA==", 47 + "license": "CC-BY-NC-4.0", 48 + "dependencies": { 49 + "@juggle/resize-observer": "^3.4.0", 50 + "d3-array": "^3.2.4", 51 + "d3-axis": "^3.0.0", 52 + "d3-brush": "^3.0.0", 53 + "d3-format": "^3.1.0", 54 + "d3-scale": "^4.0.2", 55 + "d3-selection": "^3.0.0", 56 + "d3-time-format": "^4.1.0", 57 + "d3-transition": "^3.0.1" 58 + } 59 + }, 60 + "node_modules/@cosmos.gl/graph": { 61 + "version": "3.0.0-beta.8", 62 + "resolved": "https://registry.npmjs.org/@cosmos.gl/graph/-/graph-3.0.0-beta.8.tgz", 63 + "integrity": "sha512-sxWgDtukeIXOe4Yi+Cl7/cTGufthpwu14iu3tAOUWnp4IdhaXtnclji1TkSyxLOEHgGKJ1p3k3T0VghD8MfdPA==", 64 + "license": "MIT", 65 + "dependencies": { 66 + "@luma.gl/core": "~9.2.6", 67 + "@luma.gl/engine": "~9.2.6", 68 + "@luma.gl/shadertools": "~9.2.6", 69 + "@luma.gl/webgl": "~9.2.6", 70 + "d3-array": "^3.2.0", 71 + "d3-color": "^3.1.0", 72 + "d3-drag": "^3.0.0", 73 + "d3-ease": "^3.0.1", 74 + "d3-scale": "^4.0.2", 75 + "d3-selection": "^3.0.0", 76 + "d3-transition": "^3.0.1", 77 + "d3-zoom": "^3.0.0", 78 + "dompurify": "^3.2.6", 79 + "gl-bench": "^1.0.42", 80 + "gl-matrix": "^3.4.3", 81 + "random": "^4.1.0" 82 + }, 83 + "engines": { 84 + "node": ">=18.0.0", 85 + "npm": ">=7.0.0" 86 + } 87 + }, 88 + "node_modules/@duckdb/duckdb-wasm": { 89 + "version": "1.32.0", 90 + "resolved": "https://registry.npmjs.org/@duckdb/duckdb-wasm/-/duckdb-wasm-1.32.0.tgz", 91 + "integrity": "sha512-IewXTNYEjsZCPE9weUWgtjGxUlMRo7qhX0GF6tq/KjK8bnY+RAl4cyUdYUfcdzbyb4b9ZxPC+FOsCcxgaKFWMg==", 92 + "license": "MIT", 93 + "dependencies": { 94 + "apache-arrow": "^17.0.0" 95 + } 96 + }, 97 + "node_modules/@emnapi/core": { 98 + "version": "1.10.0", 99 + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", 100 + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", 101 + "dev": true, 102 + "license": "MIT", 103 + "optional": true, 104 + "dependencies": { 105 + "@emnapi/wasi-threads": "1.2.1", 106 + "tslib": "^2.4.0" 107 + } 108 + }, 109 + "node_modules/@emnapi/runtime": { 110 + "version": "1.10.0", 111 + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", 112 + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", 113 + "dev": true, 114 + "license": "MIT", 115 + "optional": true, 116 + "dependencies": { 117 + "tslib": "^2.4.0" 118 + } 119 + }, 120 + "node_modules/@emnapi/wasi-threads": { 121 + "version": "1.2.1", 122 + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", 123 + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", 124 + "dev": true, 125 + "license": "MIT", 126 + "optional": true, 127 + "dependencies": { 128 + "tslib": "^2.4.0" 129 + } 130 + }, 131 + "node_modules/@interacta/css-labels": { 132 + "version": "0.6.0", 133 + "resolved": "https://registry.npmjs.org/@interacta/css-labels/-/css-labels-0.6.0.tgz", 134 + "integrity": "sha512-wY+UX6A1BP7MazIUkDl5rnLDETU0lhHGQsJ/Igf9BjH7ygGKvQ05348sCLbbJmsLMPoGD1P02WnTd8enYNZuHg==", 135 + "license": "MIT" 136 + }, 137 + "node_modules/@juggle/resize-observer": { 138 + "version": "3.4.0", 139 + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", 140 + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", 141 + "license": "Apache-2.0" 142 + }, 143 + "node_modules/@luma.gl/constants": { 144 + "version": "9.2.6", 145 + "resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-9.2.6.tgz", 146 + "integrity": "sha512-rvFFrJuSm5JIWbDHFuR4Q2s4eudO3Ggsv0TsGKn9eqvO7bBiPm/ANugHredvh3KviEyYuMZZxtfJvBdr3kzldg==", 147 + "license": "MIT" 148 + }, 149 + "node_modules/@luma.gl/core": { 150 + "version": "9.2.6", 151 + "resolved": "https://registry.npmjs.org/@luma.gl/core/-/core-9.2.6.tgz", 152 + "integrity": "sha512-d8KcH8ZZcjDAodSN/G2nueA9YE2X8kMz7Q0OxDGpCww6to1MZXM3Ydate/Jqsb5DDKVgUF6yD6RL8P5jOki9Yw==", 153 + "license": "MIT", 154 + "dependencies": { 155 + "@math.gl/types": "^4.1.0", 156 + "@probe.gl/env": "^4.0.8", 157 + "@probe.gl/log": "^4.0.8", 158 + "@probe.gl/stats": "^4.0.8", 159 + "@types/offscreencanvas": "^2019.6.4" 160 + } 161 + }, 162 + "node_modules/@luma.gl/engine": { 163 + "version": "9.2.6", 164 + "resolved": "https://registry.npmjs.org/@luma.gl/engine/-/engine-9.2.6.tgz", 165 + "integrity": "sha512-1AEDs2AUqOWh7Wl4onOhXmQF+Rz1zNdPXF+Kxm4aWl92RQ42Sh2CmTvRt2BJku83VQ91KFIEm/v3qd3Urzf+Uw==", 166 + "license": "MIT", 167 + "dependencies": { 168 + "@math.gl/core": "^4.1.0", 169 + "@math.gl/types": "^4.1.0", 170 + "@probe.gl/log": "^4.0.8", 171 + "@probe.gl/stats": "^4.0.8" 172 + }, 173 + "peerDependencies": { 174 + "@luma.gl/core": "~9.2.0", 175 + "@luma.gl/shadertools": "~9.2.0" 176 + } 177 + }, 178 + "node_modules/@luma.gl/shadertools": { 179 + "version": "9.2.6", 180 + "resolved": "https://registry.npmjs.org/@luma.gl/shadertools/-/shadertools-9.2.6.tgz", 181 + "integrity": "sha512-4+uUbynqPUra9d/z1nQChyHmhLgmKfSMjS7kOwLB6exSnhKnpHL3+Hu9fv55qyaX50nGH3oHawhGtJ6RRvu65w==", 182 + "license": "MIT", 183 + "dependencies": { 184 + "@math.gl/core": "^4.1.0", 185 + "@math.gl/types": "^4.1.0", 186 + "wgsl_reflect": "^1.2.0" 187 + }, 188 + "peerDependencies": { 189 + "@luma.gl/core": "~9.2.0" 190 + } 191 + }, 192 + "node_modules/@luma.gl/webgl": { 193 + "version": "9.2.6", 194 + "resolved": "https://registry.npmjs.org/@luma.gl/webgl/-/webgl-9.2.6.tgz", 195 + "integrity": "sha512-NGBTdxJMk7j8Ygr1zuTyAvr1Tw+EpupMIQo7RelFjEsZXg6pujFqiDMM+rgxex8voCeuhWBJc7Rs+MoSqd46UQ==", 196 + "license": "MIT", 197 + "dependencies": { 198 + "@luma.gl/constants": "9.2.6", 199 + "@math.gl/types": "^4.1.0", 200 + "@probe.gl/env": "^4.0.8" 201 + }, 202 + "peerDependencies": { 203 + "@luma.gl/core": "~9.2.0" 204 + } 205 + }, 206 + "node_modules/@math.gl/core": { 207 + "version": "4.1.0", 208 + "resolved": "https://registry.npmjs.org/@math.gl/core/-/core-4.1.0.tgz", 209 + "integrity": "sha512-FrdHBCVG3QdrworwrUSzXIaK+/9OCRLscxI2OUy6sLOHyHgBMyfnEGs99/m3KNvs+95BsnQLWklVfpKfQzfwKA==", 210 + "license": "MIT", 211 + "dependencies": { 212 + "@math.gl/types": "4.1.0" 213 + } 214 + }, 215 + "node_modules/@math.gl/types": { 216 + "version": "4.1.0", 217 + "resolved": "https://registry.npmjs.org/@math.gl/types/-/types-4.1.0.tgz", 218 + "integrity": "sha512-clYZdHcmRvMzVK5fjeDkQlHUzXQSNdZ7s4xOqC3nJPgz4C/TZkUecTo9YS4PruZqtDda/ag4erndP0MIn40dGA==", 219 + "license": "MIT" 220 + }, 221 + "node_modules/@napi-rs/wasm-runtime": { 222 + "version": "1.1.4", 223 + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", 224 + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", 225 + "dev": true, 226 + "license": "MIT", 227 + "optional": true, 228 + "dependencies": { 229 + "@tybys/wasm-util": "^0.10.1" 230 + }, 231 + "funding": { 232 + "type": "github", 233 + "url": "https://github.com/sponsors/Brooooooklyn" 234 + }, 235 + "peerDependencies": { 236 + "@emnapi/core": "^1.7.1", 237 + "@emnapi/runtime": "^1.7.1" 238 + } 239 + }, 240 + "node_modules/@observablehq/plot": { 241 + "version": "0.6.17", 242 + "resolved": "https://registry.npmjs.org/@observablehq/plot/-/plot-0.6.17.tgz", 243 + "integrity": "sha512-/qaXP/7mc4MUS0s4cPPFASDRjtsWp85/TbfsciqDgU1HwYixbSbbytNuInD8AcTYC3xaxACgVX06agdfQy9W+g==", 244 + "license": "ISC", 245 + "dependencies": { 246 + "d3": "^7.9.0", 247 + "interval-tree-1d": "^1.0.0", 248 + "isoformat": "^0.2.0" 249 + }, 250 + "engines": { 251 + "node": ">=12" 252 + } 253 + }, 254 + "node_modules/@oxc-project/types": { 255 + "version": "0.127.0", 256 + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", 257 + "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", 258 + "dev": true, 259 + "license": "MIT", 260 + "funding": { 261 + "url": "https://github.com/sponsors/Boshen" 262 + } 263 + }, 264 + "node_modules/@probe.gl/env": { 265 + "version": "4.1.1", 266 + "resolved": "https://registry.npmjs.org/@probe.gl/env/-/env-4.1.1.tgz", 267 + "integrity": "sha512-+68seNDMVsEegRB47pFA/Ws1Fjy8agcFYXxzorKToyPcD6zd+gZ5uhwoLd7TzsSw6Ydns//2KEszWn+EnNHTbA==", 268 + "license": "MIT" 269 + }, 270 + "node_modules/@probe.gl/log": { 271 + "version": "4.1.1", 272 + "resolved": "https://registry.npmjs.org/@probe.gl/log/-/log-4.1.1.tgz", 273 + "integrity": "sha512-kcZs9BT44pL7hS1OkRGKYRXI/SN9KejUlPD+BY40DguRLzdC5tLG/28WGMyfKdn/51GT4a0p+0P8xvDn1Ez+Kg==", 274 + "license": "MIT", 275 + "dependencies": { 276 + "@probe.gl/env": "4.1.1" 277 + } 278 + }, 279 + "node_modules/@probe.gl/stats": { 280 + "version": "4.1.1", 281 + "resolved": "https://registry.npmjs.org/@probe.gl/stats/-/stats-4.1.1.tgz", 282 + "integrity": "sha512-4VpAyMHOqydSvPlEyHwXaE+AkIdR03nX+Qhlxsk2D/IW4OVmDZgIsvJB1cDzyEEtcfKcnaEbfXeiPgejBceT6g==", 283 + "license": "MIT" 284 + }, 285 + "node_modules/@rolldown/binding-android-arm64": { 286 + "version": "1.0.0-rc.17", 287 + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", 288 + "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", 289 + "cpu": [ 290 + "arm64" 291 + ], 292 + "dev": true, 293 + "license": "MIT", 294 + "optional": true, 295 + "os": [ 296 + "android" 297 + ], 298 + "engines": { 299 + "node": "^20.19.0 || >=22.12.0" 300 + } 301 + }, 302 + "node_modules/@rolldown/binding-darwin-arm64": { 303 + "version": "1.0.0-rc.17", 304 + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", 305 + "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", 306 + "cpu": [ 307 + "arm64" 308 + ], 309 + "dev": true, 310 + "license": "MIT", 311 + "optional": true, 312 + "os": [ 313 + "darwin" 314 + ], 315 + "engines": { 316 + "node": "^20.19.0 || >=22.12.0" 317 + } 318 + }, 319 + "node_modules/@rolldown/binding-darwin-x64": { 320 + "version": "1.0.0-rc.17", 321 + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", 322 + "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", 323 + "cpu": [ 324 + "x64" 325 + ], 326 + "dev": true, 327 + "license": "MIT", 328 + "optional": true, 329 + "os": [ 330 + "darwin" 331 + ], 332 + "engines": { 333 + "node": "^20.19.0 || >=22.12.0" 334 + } 335 + }, 336 + "node_modules/@rolldown/binding-freebsd-x64": { 337 + "version": "1.0.0-rc.17", 338 + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", 339 + "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", 340 + "cpu": [ 341 + "x64" 342 + ], 343 + "dev": true, 344 + "license": "MIT", 345 + "optional": true, 346 + "os": [ 347 + "freebsd" 348 + ], 349 + "engines": { 350 + "node": "^20.19.0 || >=22.12.0" 351 + } 352 + }, 353 + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { 354 + "version": "1.0.0-rc.17", 355 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", 356 + "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", 357 + "cpu": [ 358 + "arm" 359 + ], 360 + "dev": true, 361 + "license": "MIT", 362 + "optional": true, 363 + "os": [ 364 + "linux" 365 + ], 366 + "engines": { 367 + "node": "^20.19.0 || >=22.12.0" 368 + } 369 + }, 370 + "node_modules/@rolldown/binding-linux-arm64-gnu": { 371 + "version": "1.0.0-rc.17", 372 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", 373 + "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", 374 + "cpu": [ 375 + "arm64" 376 + ], 377 + "dev": true, 378 + "libc": [ 379 + "glibc" 380 + ], 381 + "license": "MIT", 382 + "optional": true, 383 + "os": [ 384 + "linux" 385 + ], 386 + "engines": { 387 + "node": "^20.19.0 || >=22.12.0" 388 + } 389 + }, 390 + "node_modules/@rolldown/binding-linux-arm64-musl": { 391 + "version": "1.0.0-rc.17", 392 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", 393 + "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", 394 + "cpu": [ 395 + "arm64" 396 + ], 397 + "dev": true, 398 + "libc": [ 399 + "musl" 400 + ], 401 + "license": "MIT", 402 + "optional": true, 403 + "os": [ 404 + "linux" 405 + ], 406 + "engines": { 407 + "node": "^20.19.0 || >=22.12.0" 408 + } 409 + }, 410 + "node_modules/@rolldown/binding-linux-ppc64-gnu": { 411 + "version": "1.0.0-rc.17", 412 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", 413 + "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", 414 + "cpu": [ 415 + "ppc64" 416 + ], 417 + "dev": true, 418 + "libc": [ 419 + "glibc" 420 + ], 421 + "license": "MIT", 422 + "optional": true, 423 + "os": [ 424 + "linux" 425 + ], 426 + "engines": { 427 + "node": "^20.19.0 || >=22.12.0" 428 + } 429 + }, 430 + "node_modules/@rolldown/binding-linux-s390x-gnu": { 431 + "version": "1.0.0-rc.17", 432 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", 433 + "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", 434 + "cpu": [ 435 + "s390x" 436 + ], 437 + "dev": true, 438 + "libc": [ 439 + "glibc" 440 + ], 441 + "license": "MIT", 442 + "optional": true, 443 + "os": [ 444 + "linux" 445 + ], 446 + "engines": { 447 + "node": "^20.19.0 || >=22.12.0" 448 + } 449 + }, 450 + "node_modules/@rolldown/binding-linux-x64-gnu": { 451 + "version": "1.0.0-rc.17", 452 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", 453 + "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", 454 + "cpu": [ 455 + "x64" 456 + ], 457 + "dev": true, 458 + "libc": [ 459 + "glibc" 460 + ], 461 + "license": "MIT", 462 + "optional": true, 463 + "os": [ 464 + "linux" 465 + ], 466 + "engines": { 467 + "node": "^20.19.0 || >=22.12.0" 468 + } 469 + }, 470 + "node_modules/@rolldown/binding-linux-x64-musl": { 471 + "version": "1.0.0-rc.17", 472 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", 473 + "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", 474 + "cpu": [ 475 + "x64" 476 + ], 477 + "dev": true, 478 + "libc": [ 479 + "musl" 480 + ], 481 + "license": "MIT", 482 + "optional": true, 483 + "os": [ 484 + "linux" 485 + ], 486 + "engines": { 487 + "node": "^20.19.0 || >=22.12.0" 488 + } 489 + }, 490 + "node_modules/@rolldown/binding-openharmony-arm64": { 491 + "version": "1.0.0-rc.17", 492 + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", 493 + "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", 494 + "cpu": [ 495 + "arm64" 496 + ], 497 + "dev": true, 498 + "license": "MIT", 499 + "optional": true, 500 + "os": [ 501 + "openharmony" 502 + ], 503 + "engines": { 504 + "node": "^20.19.0 || >=22.12.0" 505 + } 506 + }, 507 + "node_modules/@rolldown/binding-wasm32-wasi": { 508 + "version": "1.0.0-rc.17", 509 + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", 510 + "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", 511 + "cpu": [ 512 + "wasm32" 513 + ], 514 + "dev": true, 515 + "license": "MIT", 516 + "optional": true, 517 + "dependencies": { 518 + "@emnapi/core": "1.10.0", 519 + "@emnapi/runtime": "1.10.0", 520 + "@napi-rs/wasm-runtime": "^1.1.4" 521 + }, 522 + "engines": { 523 + "node": "^20.19.0 || >=22.12.0" 524 + } 525 + }, 526 + "node_modules/@rolldown/binding-win32-arm64-msvc": { 527 + "version": "1.0.0-rc.17", 528 + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", 529 + "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", 530 + "cpu": [ 531 + "arm64" 532 + ], 533 + "dev": true, 534 + "license": "MIT", 535 + "optional": true, 536 + "os": [ 537 + "win32" 538 + ], 539 + "engines": { 540 + "node": "^20.19.0 || >=22.12.0" 541 + } 542 + }, 543 + "node_modules/@rolldown/binding-win32-x64-msvc": { 544 + "version": "1.0.0-rc.17", 545 + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", 546 + "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", 547 + "cpu": [ 548 + "x64" 549 + ], 550 + "dev": true, 551 + "license": "MIT", 552 + "optional": true, 553 + "os": [ 554 + "win32" 555 + ], 556 + "engines": { 557 + "node": "^20.19.0 || >=22.12.0" 558 + } 559 + }, 560 + "node_modules/@rolldown/pluginutils": { 561 + "version": "1.0.0-rc.17", 562 + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", 563 + "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", 564 + "dev": true, 565 + "license": "MIT" 566 + }, 567 + "node_modules/@supabase/auth-js": { 568 + "version": "2.105.1", 569 + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.105.1.tgz", 570 + "integrity": "sha512-zc4s8Xg4truwE1Q4Q8M8oUVDARMd05pKh73NyQsMbYU1HDdDN2iiKzena/yu+yJze3WrD4c092FdckPiK1rLQw==", 571 + "license": "MIT", 572 + "dependencies": { 573 + "tslib": "2.8.1" 574 + }, 575 + "engines": { 576 + "node": ">=20.0.0" 577 + } 578 + }, 579 + "node_modules/@supabase/functions-js": { 580 + "version": "2.105.1", 581 + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.105.1.tgz", 582 + "integrity": "sha512-dTk1e7oE51VGc1lS2S0J0NLo0Wp4JYChj74ArJKbIWgoWuFwO0wcJYjeyOV3AAEpKst8/LQWUZOUKO1tRXBrpA==", 583 + "license": "MIT", 584 + "dependencies": { 585 + "tslib": "2.8.1" 586 + }, 587 + "engines": { 588 + "node": ">=20.0.0" 589 + } 590 + }, 591 + "node_modules/@supabase/phoenix": { 592 + "version": "0.4.1", 593 + "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.1.tgz", 594 + "integrity": "sha512-hWGJkDAfWUNY8k0C080u3sGNFd2ncl9erhKgP7hnGkgJWEfT5Pd/SXal4QmWXBECVlZrannMAc9sBaaRyWpiUA==", 595 + "license": "MIT" 596 + }, 597 + "node_modules/@supabase/postgrest-js": { 598 + "version": "2.105.1", 599 + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.105.1.tgz", 600 + "integrity": "sha512-6SbtsoWC55xfsm7gbfLqvF+yIwTQEbjt+jFGf4klDpwSnUy17Hv5x0Dq52oqwTQlw6Ta0h1D5gTP0/pApqNojA==", 601 + "license": "MIT", 602 + "dependencies": { 603 + "tslib": "2.8.1" 604 + }, 605 + "engines": { 606 + "node": ">=20.0.0" 607 + } 608 + }, 609 + "node_modules/@supabase/realtime-js": { 610 + "version": "2.105.1", 611 + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.105.1.tgz", 612 + "integrity": "sha512-3X3cUEl5cJ4lRQHr1hXHx0b98OaL97RRO2vrRZ98FD91JV/MquZHhrGJSv/+IkOnjF6E2e0RUOxE8P3Zi035ow==", 613 + "license": "MIT", 614 + "dependencies": { 615 + "@supabase/phoenix": "^0.4.1", 616 + "@types/ws": "^8.18.1", 617 + "tslib": "2.8.1", 618 + "ws": "^8.18.2" 619 + }, 620 + "engines": { 621 + "node": ">=20.0.0" 622 + } 623 + }, 624 + "node_modules/@supabase/storage-js": { 625 + "version": "2.105.1", 626 + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.105.1.tgz", 627 + "integrity": "sha512-owfdCNH5ikXXDusjzsgU6LavEBqGUoueOnL/9XIucld70/WJ/rbqp89K//c9QPICDNuegsmpoeasydDAiucLKQ==", 628 + "license": "MIT", 629 + "dependencies": { 630 + "iceberg-js": "^0.8.1", 631 + "tslib": "2.8.1" 632 + }, 633 + "engines": { 634 + "node": ">=20.0.0" 635 + } 636 + }, 637 + "node_modules/@supabase/supabase-js": { 638 + "version": "2.105.1", 639 + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.105.1.tgz", 640 + "integrity": "sha512-4gn6HmsAkCCVU7p8JmgKGhHJ5Btod4ZzSp8qKZf4JHaTxbhaIK86/usHzeLxWv7EJJDhBmILDmJOSOf9iF4CLA==", 641 + "license": "MIT", 642 + "dependencies": { 643 + "@supabase/auth-js": "2.105.1", 644 + "@supabase/functions-js": "2.105.1", 645 + "@supabase/postgrest-js": "2.105.1", 646 + "@supabase/realtime-js": "2.105.1", 647 + "@supabase/storage-js": "2.105.1" 648 + }, 649 + "engines": { 650 + "node": ">=20.0.0" 651 + } 652 + }, 653 + "node_modules/@swc/helpers": { 654 + "version": "0.5.21", 655 + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz", 656 + "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==", 657 + "license": "Apache-2.0", 658 + "dependencies": { 659 + "tslib": "^2.8.0" 660 + } 661 + }, 662 + "node_modules/@tybys/wasm-util": { 663 + "version": "0.10.1", 664 + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", 665 + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", 666 + "dev": true, 667 + "license": "MIT", 668 + "optional": true, 669 + "dependencies": { 670 + "tslib": "^2.4.0" 671 + } 672 + }, 673 + "node_modules/@types/command-line-args": { 674 + "version": "5.2.3", 675 + "resolved": "https://registry.npmjs.org/@types/command-line-args/-/command-line-args-5.2.3.tgz", 676 + "integrity": "sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==", 677 + "license": "MIT" 678 + }, 679 + "node_modules/@types/command-line-usage": { 680 + "version": "5.0.4", 681 + "resolved": "https://registry.npmjs.org/@types/command-line-usage/-/command-line-usage-5.0.4.tgz", 682 + "integrity": "sha512-BwR5KP3Es/CSht0xqBcUXS3qCAUVXwpRKsV2+arxeb65atasuXG9LykC9Ab10Cw3s2raH92ZqOeILaQbsB2ACg==", 683 + "license": "MIT" 684 + }, 685 + "node_modules/@types/node": { 686 + "version": "25.6.0", 687 + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", 688 + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", 689 + "license": "MIT", 690 + "dependencies": { 691 + "undici-types": "~7.19.0" 692 + } 693 + }, 694 + "node_modules/@types/offscreencanvas": { 695 + "version": "2019.7.3", 696 + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", 697 + "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==", 698 + "license": "MIT" 699 + }, 700 + "node_modules/@types/trusted-types": { 701 + "version": "2.0.7", 702 + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", 703 + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", 704 + "license": "MIT", 705 + "optional": true 706 + }, 707 + "node_modules/@types/ws": { 708 + "version": "8.18.1", 709 + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", 710 + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", 711 + "license": "MIT", 712 + "dependencies": { 713 + "@types/node": "*" 714 + } 715 + }, 716 + "node_modules/@uwdata/flechette": { 717 + "version": "2.4.0", 718 + "resolved": "https://registry.npmjs.org/@uwdata/flechette/-/flechette-2.4.0.tgz", 719 + "integrity": "sha512-cwxeYWe2iua4zPUopiqhYXkxaQWr0GK4Kbak7i0zemo3sgmNJ3IE+pNi6XD1dOK7q50SEXTLwieP5JycG6D56A==", 720 + "license": "BSD-3-Clause" 721 + }, 722 + "node_modules/@uwdata/mosaic-core": { 723 + "version": "0.21.1", 724 + "resolved": "https://registry.npmjs.org/@uwdata/mosaic-core/-/mosaic-core-0.21.1.tgz", 725 + "integrity": "sha512-nRh93+A7U/06x/6boSLUUaSCo4pkNAjOgV8P2Zl6ZZeF5pWynLcSqT30WLQPx+VewTvFgJGCymocymdbirFxYw==", 726 + "license": "BSD-3-Clause", 727 + "dependencies": { 728 + "@duckdb/duckdb-wasm": "1.30.0", 729 + "@uwdata/flechette": "^2.2.5", 730 + "@uwdata/mosaic-sql": "^0.21.1" 731 + } 732 + }, 733 + "node_modules/@uwdata/mosaic-core/node_modules/@duckdb/duckdb-wasm": { 734 + "version": "1.30.0", 735 + "resolved": "https://registry.npmjs.org/@duckdb/duckdb-wasm/-/duckdb-wasm-1.30.0.tgz", 736 + "integrity": "sha512-9aWrm+4ayl4sTlvGtl/b+LxrUyXaac3yyVqkoJ3F7Vkd62PoS8PcQIRJ/KjXBW36LP1CnPY5jjvFyIcTFLtcXA==", 737 + "license": "MIT", 738 + "dependencies": { 739 + "apache-arrow": "^17.0.0" 740 + } 741 + }, 742 + "node_modules/@uwdata/mosaic-inputs": { 743 + "version": "0.21.1", 744 + "resolved": "https://registry.npmjs.org/@uwdata/mosaic-inputs/-/mosaic-inputs-0.21.1.tgz", 745 + "integrity": "sha512-9h/PFk71QL5+Nhqsai9pqVdnHUQb8OAemZrPossbfvb3q62o7RpU+jMwxcAId4uB/qRTAFxr0LgRfoPxrjfZYg==", 746 + "license": "BSD-3-Clause", 747 + "dependencies": { 748 + "@uwdata/mosaic-core": "^0.21.1", 749 + "@uwdata/mosaic-sql": "^0.21.1" 750 + } 751 + }, 752 + "node_modules/@uwdata/mosaic-plot": { 753 + "version": "0.21.1", 754 + "resolved": "https://registry.npmjs.org/@uwdata/mosaic-plot/-/mosaic-plot-0.21.1.tgz", 755 + "integrity": "sha512-ZPBD0Km44VIexZ7l88n1yWh8QpVJakJnmkyq6zr8aujw2i2+MkrcI8Abc2aPJ74o2YiSb9hMKxno83nB/Mfy7A==", 756 + "license": "BSD-3-Clause", 757 + "dependencies": { 758 + "@observablehq/plot": "^0.6.17", 759 + "@uwdata/mosaic-core": "^0.21.1", 760 + "@uwdata/mosaic-sql": "^0.21.1", 761 + "d3": "^7.9.0" 762 + } 763 + }, 764 + "node_modules/@uwdata/mosaic-sql": { 765 + "version": "0.21.1", 766 + "resolved": "https://registry.npmjs.org/@uwdata/mosaic-sql/-/mosaic-sql-0.21.1.tgz", 767 + "integrity": "sha512-2B4Dle4odyxIaBaDVRfQchebH/CUZvUV8kIwKF3V2GksQoF8KYY/Q6zTLTJhYrDEdUkt8M0OIy4U/Ntw93CV1A==", 768 + "license": "BSD-3-Clause" 769 + }, 770 + "node_modules/@uwdata/vgplot": { 771 + "version": "0.21.1", 772 + "resolved": "https://registry.npmjs.org/@uwdata/vgplot/-/vgplot-0.21.1.tgz", 773 + "integrity": "sha512-R+CFYeTPdNoMzMAxNwt7coTqcWWY6aOKUj28SqkQ4dbL3Ig37RjCnRDcpFzCqvxaSeOwZRXQ1Ld3WhnagqDh/Q==", 774 + "license": "BSD-3-Clause", 775 + "dependencies": { 776 + "@uwdata/mosaic-core": "^0.21.1", 777 + "@uwdata/mosaic-inputs": "^0.21.1", 778 + "@uwdata/mosaic-plot": "^0.21.1", 779 + "@uwdata/mosaic-sql": "^0.21.1" 780 + } 781 + }, 782 + "node_modules/ansi-styles": { 783 + "version": "4.3.0", 784 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 785 + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 786 + "license": "MIT", 787 + "dependencies": { 788 + "color-convert": "^2.0.1" 789 + }, 790 + "engines": { 791 + "node": ">=8" 792 + }, 793 + "funding": { 794 + "url": "https://github.com/chalk/ansi-styles?sponsor=1" 795 + } 796 + }, 797 + "node_modules/apache-arrow": { 798 + "version": "17.0.0", 799 + "resolved": "https://registry.npmjs.org/apache-arrow/-/apache-arrow-17.0.0.tgz", 800 + "integrity": "sha512-X0p7auzdnGuhYMVKYINdQssS4EcKec9TCXyez/qtJt32DrIMGbzqiaMiQ0X6fQlQpw8Fl0Qygcv4dfRAr5Gu9Q==", 801 + "license": "Apache-2.0", 802 + "dependencies": { 803 + "@swc/helpers": "^0.5.11", 804 + "@types/command-line-args": "^5.2.3", 805 + "@types/command-line-usage": "^5.0.4", 806 + "@types/node": "^20.13.0", 807 + "command-line-args": "^5.2.1", 808 + "command-line-usage": "^7.0.1", 809 + "flatbuffers": "^24.3.25", 810 + "json-bignum": "^0.0.3", 811 + "tslib": "^2.6.2" 812 + }, 813 + "bin": { 814 + "arrow2csv": "bin/arrow2csv.cjs" 815 + } 816 + }, 817 + "node_modules/apache-arrow/node_modules/@types/node": { 818 + "version": "20.19.39", 819 + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", 820 + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", 821 + "license": "MIT", 822 + "dependencies": { 823 + "undici-types": "~6.21.0" 824 + } 825 + }, 826 + "node_modules/apache-arrow/node_modules/undici-types": { 827 + "version": "6.21.0", 828 + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", 829 + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", 830 + "license": "MIT" 831 + }, 832 + "node_modules/array-back": { 833 + "version": "3.1.0", 834 + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", 835 + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", 836 + "license": "MIT", 837 + "engines": { 838 + "node": ">=6" 839 + } 840 + }, 841 + "node_modules/binary-search-bounds": { 842 + "version": "2.0.5", 843 + "resolved": "https://registry.npmjs.org/binary-search-bounds/-/binary-search-bounds-2.0.5.tgz", 844 + "integrity": "sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA==", 845 + "license": "MIT" 846 + }, 847 + "node_modules/chalk": { 848 + "version": "4.1.2", 849 + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 850 + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 851 + "license": "MIT", 852 + "dependencies": { 853 + "ansi-styles": "^4.1.0", 854 + "supports-color": "^7.1.0" 855 + }, 856 + "engines": { 857 + "node": ">=10" 858 + }, 859 + "funding": { 860 + "url": "https://github.com/chalk/chalk?sponsor=1" 861 + } 862 + }, 863 + "node_modules/chalk-template": { 864 + "version": "0.4.0", 865 + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", 866 + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", 867 + "license": "MIT", 868 + "dependencies": { 869 + "chalk": "^4.1.2" 870 + }, 871 + "engines": { 872 + "node": ">=12" 873 + }, 874 + "funding": { 875 + "url": "https://github.com/chalk/chalk-template?sponsor=1" 876 + } 877 + }, 878 + "node_modules/color-convert": { 879 + "version": "2.0.1", 880 + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 881 + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 882 + "license": "MIT", 883 + "dependencies": { 884 + "color-name": "~1.1.4" 885 + }, 886 + "engines": { 887 + "node": ">=7.0.0" 888 + } 889 + }, 890 + "node_modules/color-name": { 891 + "version": "1.1.4", 892 + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 893 + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 894 + "license": "MIT" 895 + }, 896 + "node_modules/command-line-args": { 897 + "version": "5.2.1", 898 + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", 899 + "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", 900 + "license": "MIT", 901 + "dependencies": { 902 + "array-back": "^3.1.0", 903 + "find-replace": "^3.0.0", 904 + "lodash.camelcase": "^4.3.0", 905 + "typical": "^4.0.0" 906 + }, 907 + "engines": { 908 + "node": ">=4.0.0" 909 + } 910 + }, 911 + "node_modules/command-line-usage": { 912 + "version": "7.0.4", 913 + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-7.0.4.tgz", 914 + "integrity": "sha512-85UdvzTNx/+s5CkSgBm/0hzP80RFHAa7PsfeADE5ezZF3uHz3/Tqj9gIKGT9PTtpycc3Ua64T0oVulGfKxzfqg==", 915 + "license": "MIT", 916 + "dependencies": { 917 + "array-back": "^6.2.2", 918 + "chalk-template": "^0.4.0", 919 + "table-layout": "^4.1.1", 920 + "typical": "^7.3.0" 921 + }, 922 + "engines": { 923 + "node": ">=12.20.0" 924 + } 925 + }, 926 + "node_modules/command-line-usage/node_modules/array-back": { 927 + "version": "6.2.3", 928 + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.3.tgz", 929 + "integrity": "sha512-SGDvmg6QTYiTxCBkYVmThcoa67uLl35pyzRHdpCGBOcqFy6BtwnphoFPk7LhJshD+Yk1Kt35WGWeZPTgwR4Fhw==", 930 + "license": "MIT", 931 + "engines": { 932 + "node": ">=12.17" 933 + } 934 + }, 935 + "node_modules/command-line-usage/node_modules/typical": { 936 + "version": "7.3.0", 937 + "resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz", 938 + "integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==", 939 + "license": "MIT", 940 + "engines": { 941 + "node": ">=12.17" 942 + } 943 + }, 944 + "node_modules/commander": { 945 + "version": "7.2.0", 946 + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", 947 + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", 948 + "license": "MIT", 949 + "engines": { 950 + "node": ">= 10" 951 + } 952 + }, 953 + "node_modules/d3": { 954 + "version": "7.9.0", 955 + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", 956 + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", 957 + "license": "ISC", 958 + "dependencies": { 959 + "d3-array": "3", 960 + "d3-axis": "3", 961 + "d3-brush": "3", 962 + "d3-chord": "3", 963 + "d3-color": "3", 964 + "d3-contour": "4", 965 + "d3-delaunay": "6", 966 + "d3-dispatch": "3", 967 + "d3-drag": "3", 968 + "d3-dsv": "3", 969 + "d3-ease": "3", 970 + "d3-fetch": "3", 971 + "d3-force": "3", 972 + "d3-format": "3", 973 + "d3-geo": "3", 974 + "d3-hierarchy": "3", 975 + "d3-interpolate": "3", 976 + "d3-path": "3", 977 + "d3-polygon": "3", 978 + "d3-quadtree": "3", 979 + "d3-random": "3", 980 + "d3-scale": "4", 981 + "d3-scale-chromatic": "3", 982 + "d3-selection": "3", 983 + "d3-shape": "3", 984 + "d3-time": "3", 985 + "d3-time-format": "4", 986 + "d3-timer": "3", 987 + "d3-transition": "3", 988 + "d3-zoom": "3" 989 + }, 990 + "engines": { 991 + "node": ">=12" 992 + } 993 + }, 994 + "node_modules/d3-array": { 995 + "version": "3.2.4", 996 + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", 997 + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", 998 + "license": "ISC", 999 + "dependencies": { 1000 + "internmap": "1 - 2" 1001 + }, 1002 + "engines": { 1003 + "node": ">=12" 1004 + } 1005 + }, 1006 + "node_modules/d3-axis": { 1007 + "version": "3.0.0", 1008 + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", 1009 + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", 1010 + "license": "ISC", 1011 + "engines": { 1012 + "node": ">=12" 1013 + } 1014 + }, 1015 + "node_modules/d3-brush": { 1016 + "version": "3.0.0", 1017 + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", 1018 + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", 1019 + "license": "ISC", 1020 + "dependencies": { 1021 + "d3-dispatch": "1 - 3", 1022 + "d3-drag": "2 - 3", 1023 + "d3-interpolate": "1 - 3", 1024 + "d3-selection": "3", 1025 + "d3-transition": "3" 1026 + }, 1027 + "engines": { 1028 + "node": ">=12" 1029 + } 1030 + }, 1031 + "node_modules/d3-chord": { 1032 + "version": "3.0.1", 1033 + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", 1034 + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", 1035 + "license": "ISC", 1036 + "dependencies": { 1037 + "d3-path": "1 - 3" 1038 + }, 1039 + "engines": { 1040 + "node": ">=12" 1041 + } 1042 + }, 1043 + "node_modules/d3-color": { 1044 + "version": "3.1.0", 1045 + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", 1046 + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", 1047 + "license": "ISC", 1048 + "engines": { 1049 + "node": ">=12" 1050 + } 1051 + }, 1052 + "node_modules/d3-contour": { 1053 + "version": "4.0.2", 1054 + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", 1055 + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", 1056 + "license": "ISC", 1057 + "dependencies": { 1058 + "d3-array": "^3.2.0" 1059 + }, 1060 + "engines": { 1061 + "node": ">=12" 1062 + } 1063 + }, 1064 + "node_modules/d3-delaunay": { 1065 + "version": "6.0.4", 1066 + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", 1067 + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", 1068 + "license": "ISC", 1069 + "dependencies": { 1070 + "delaunator": "5" 1071 + }, 1072 + "engines": { 1073 + "node": ">=12" 1074 + } 1075 + }, 1076 + "node_modules/d3-dispatch": { 1077 + "version": "3.0.1", 1078 + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", 1079 + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", 1080 + "license": "ISC", 1081 + "engines": { 1082 + "node": ">=12" 1083 + } 1084 + }, 1085 + "node_modules/d3-drag": { 1086 + "version": "3.0.0", 1087 + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", 1088 + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", 1089 + "license": "ISC", 1090 + "dependencies": { 1091 + "d3-dispatch": "1 - 3", 1092 + "d3-selection": "3" 1093 + }, 1094 + "engines": { 1095 + "node": ">=12" 1096 + } 1097 + }, 1098 + "node_modules/d3-dsv": { 1099 + "version": "3.0.1", 1100 + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", 1101 + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", 1102 + "license": "ISC", 1103 + "dependencies": { 1104 + "commander": "7", 1105 + "iconv-lite": "0.6", 1106 + "rw": "1" 1107 + }, 1108 + "bin": { 1109 + "csv2json": "bin/dsv2json.js", 1110 + "csv2tsv": "bin/dsv2dsv.js", 1111 + "dsv2dsv": "bin/dsv2dsv.js", 1112 + "dsv2json": "bin/dsv2json.js", 1113 + "json2csv": "bin/json2dsv.js", 1114 + "json2dsv": "bin/json2dsv.js", 1115 + "json2tsv": "bin/json2dsv.js", 1116 + "tsv2csv": "bin/dsv2dsv.js", 1117 + "tsv2json": "bin/dsv2json.js" 1118 + }, 1119 + "engines": { 1120 + "node": ">=12" 1121 + } 1122 + }, 1123 + "node_modules/d3-ease": { 1124 + "version": "3.0.1", 1125 + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", 1126 + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", 1127 + "license": "BSD-3-Clause", 1128 + "engines": { 1129 + "node": ">=12" 1130 + } 1131 + }, 1132 + "node_modules/d3-fetch": { 1133 + "version": "3.0.1", 1134 + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", 1135 + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", 1136 + "license": "ISC", 1137 + "dependencies": { 1138 + "d3-dsv": "1 - 3" 1139 + }, 1140 + "engines": { 1141 + "node": ">=12" 1142 + } 1143 + }, 1144 + "node_modules/d3-force": { 1145 + "version": "3.0.0", 1146 + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", 1147 + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", 1148 + "license": "ISC", 1149 + "dependencies": { 1150 + "d3-dispatch": "1 - 3", 1151 + "d3-quadtree": "1 - 3", 1152 + "d3-timer": "1 - 3" 1153 + }, 1154 + "engines": { 1155 + "node": ">=12" 1156 + } 1157 + }, 1158 + "node_modules/d3-format": { 1159 + "version": "3.1.2", 1160 + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", 1161 + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", 1162 + "license": "ISC", 1163 + "engines": { 1164 + "node": ">=12" 1165 + } 1166 + }, 1167 + "node_modules/d3-geo": { 1168 + "version": "3.1.1", 1169 + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", 1170 + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", 1171 + "license": "ISC", 1172 + "dependencies": { 1173 + "d3-array": "2.5.0 - 3" 1174 + }, 1175 + "engines": { 1176 + "node": ">=12" 1177 + } 1178 + }, 1179 + "node_modules/d3-hierarchy": { 1180 + "version": "3.1.2", 1181 + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", 1182 + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", 1183 + "license": "ISC", 1184 + "engines": { 1185 + "node": ">=12" 1186 + } 1187 + }, 1188 + "node_modules/d3-interpolate": { 1189 + "version": "3.0.1", 1190 + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", 1191 + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", 1192 + "license": "ISC", 1193 + "dependencies": { 1194 + "d3-color": "1 - 3" 1195 + }, 1196 + "engines": { 1197 + "node": ">=12" 1198 + } 1199 + }, 1200 + "node_modules/d3-path": { 1201 + "version": "3.1.0", 1202 + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", 1203 + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", 1204 + "license": "ISC", 1205 + "engines": { 1206 + "node": ">=12" 1207 + } 1208 + }, 1209 + "node_modules/d3-polygon": { 1210 + "version": "3.0.1", 1211 + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", 1212 + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", 1213 + "license": "ISC", 1214 + "engines": { 1215 + "node": ">=12" 1216 + } 1217 + }, 1218 + "node_modules/d3-quadtree": { 1219 + "version": "3.0.1", 1220 + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", 1221 + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", 1222 + "license": "ISC", 1223 + "engines": { 1224 + "node": ">=12" 1225 + } 1226 + }, 1227 + "node_modules/d3-random": { 1228 + "version": "3.0.1", 1229 + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", 1230 + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", 1231 + "license": "ISC", 1232 + "engines": { 1233 + "node": ">=12" 1234 + } 1235 + }, 1236 + "node_modules/d3-scale": { 1237 + "version": "4.0.2", 1238 + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", 1239 + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", 1240 + "license": "ISC", 1241 + "dependencies": { 1242 + "d3-array": "2.10.0 - 3", 1243 + "d3-format": "1 - 3", 1244 + "d3-interpolate": "1.2.0 - 3", 1245 + "d3-time": "2.1.1 - 3", 1246 + "d3-time-format": "2 - 4" 1247 + }, 1248 + "engines": { 1249 + "node": ">=12" 1250 + } 1251 + }, 1252 + "node_modules/d3-scale-chromatic": { 1253 + "version": "3.1.0", 1254 + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", 1255 + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", 1256 + "license": "ISC", 1257 + "dependencies": { 1258 + "d3-color": "1 - 3", 1259 + "d3-interpolate": "1 - 3" 1260 + }, 1261 + "engines": { 1262 + "node": ">=12" 1263 + } 1264 + }, 1265 + "node_modules/d3-selection": { 1266 + "version": "3.0.0", 1267 + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", 1268 + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", 1269 + "license": "ISC", 1270 + "engines": { 1271 + "node": ">=12" 1272 + } 1273 + }, 1274 + "node_modules/d3-shape": { 1275 + "version": "3.2.0", 1276 + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", 1277 + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", 1278 + "license": "ISC", 1279 + "dependencies": { 1280 + "d3-path": "^3.1.0" 1281 + }, 1282 + "engines": { 1283 + "node": ">=12" 1284 + } 1285 + }, 1286 + "node_modules/d3-time": { 1287 + "version": "3.1.0", 1288 + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", 1289 + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", 1290 + "license": "ISC", 1291 + "dependencies": { 1292 + "d3-array": "2 - 3" 1293 + }, 1294 + "engines": { 1295 + "node": ">=12" 1296 + } 1297 + }, 1298 + "node_modules/d3-time-format": { 1299 + "version": "4.1.0", 1300 + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", 1301 + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", 1302 + "license": "ISC", 1303 + "dependencies": { 1304 + "d3-time": "1 - 3" 1305 + }, 1306 + "engines": { 1307 + "node": ">=12" 1308 + } 1309 + }, 1310 + "node_modules/d3-timer": { 1311 + "version": "3.0.1", 1312 + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", 1313 + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", 1314 + "license": "ISC", 1315 + "engines": { 1316 + "node": ">=12" 1317 + } 1318 + }, 1319 + "node_modules/d3-transition": { 1320 + "version": "3.0.1", 1321 + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", 1322 + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", 1323 + "license": "ISC", 1324 + "dependencies": { 1325 + "d3-color": "1 - 3", 1326 + "d3-dispatch": "1 - 3", 1327 + "d3-ease": "1 - 3", 1328 + "d3-interpolate": "1 - 3", 1329 + "d3-timer": "1 - 3" 1330 + }, 1331 + "engines": { 1332 + "node": ">=12" 1333 + }, 1334 + "peerDependencies": { 1335 + "d3-selection": "2 - 3" 1336 + } 1337 + }, 1338 + "node_modules/d3-zoom": { 1339 + "version": "3.0.0", 1340 + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", 1341 + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", 1342 + "license": "ISC", 1343 + "dependencies": { 1344 + "d3-dispatch": "1 - 3", 1345 + "d3-drag": "2 - 3", 1346 + "d3-interpolate": "1 - 3", 1347 + "d3-selection": "2 - 3", 1348 + "d3-transition": "2 - 3" 1349 + }, 1350 + "engines": { 1351 + "node": ">=12" 1352 + } 1353 + }, 1354 + "node_modules/delaunator": { 1355 + "version": "5.1.0", 1356 + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", 1357 + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", 1358 + "license": "ISC", 1359 + "dependencies": { 1360 + "robust-predicates": "^3.0.2" 1361 + } 1362 + }, 1363 + "node_modules/detect-libc": { 1364 + "version": "2.1.2", 1365 + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", 1366 + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", 1367 + "dev": true, 1368 + "license": "Apache-2.0", 1369 + "engines": { 1370 + "node": ">=8" 1371 + } 1372 + }, 1373 + "node_modules/dompurify": { 1374 + "version": "3.4.2", 1375 + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.2.tgz", 1376 + "integrity": "sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==", 1377 + "license": "(MPL-2.0 OR Apache-2.0)", 1378 + "optionalDependencies": { 1379 + "@types/trusted-types": "^2.0.7" 1380 + } 1381 + }, 1382 + "node_modules/fdir": { 1383 + "version": "6.5.0", 1384 + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", 1385 + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", 1386 + "dev": true, 1387 + "license": "MIT", 1388 + "engines": { 1389 + "node": ">=12.0.0" 1390 + }, 1391 + "peerDependencies": { 1392 + "picomatch": "^3 || ^4" 1393 + }, 1394 + "peerDependenciesMeta": { 1395 + "picomatch": { 1396 + "optional": true 1397 + } 1398 + } 1399 + }, 1400 + "node_modules/find-replace": { 1401 + "version": "3.0.0", 1402 + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", 1403 + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", 1404 + "license": "MIT", 1405 + "dependencies": { 1406 + "array-back": "^3.0.1" 1407 + }, 1408 + "engines": { 1409 + "node": ">=4.0.0" 1410 + } 1411 + }, 1412 + "node_modules/flatbuffers": { 1413 + "version": "24.12.23", 1414 + "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-24.12.23.tgz", 1415 + "integrity": "sha512-dLVCAISd5mhls514keQzmEG6QHmUUsNuWsb4tFafIUwvvgDjXhtfAYSKOzt5SWOy+qByV5pbsDZ+Vb7HUOBEdA==", 1416 + "license": "Apache-2.0" 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/gl-bench": { 1434 + "version": "1.0.42", 1435 + "resolved": "https://registry.npmjs.org/gl-bench/-/gl-bench-1.0.42.tgz", 1436 + "integrity": "sha512-zuMsA/NCPmI8dPy6q3zTUH8OUM5cqKg7uVWwqzrtXJPBqoypM0XeFWEc8iFOqbf/1qtXieWOrbmgFEByKTQt4Q==", 1437 + "license": "MIT" 1438 + }, 1439 + "node_modules/gl-matrix": { 1440 + "version": "3.4.4", 1441 + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", 1442 + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", 1443 + "license": "MIT" 1444 + }, 1445 + "node_modules/has-flag": { 1446 + "version": "4.0.0", 1447 + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 1448 + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 1449 + "license": "MIT", 1450 + "engines": { 1451 + "node": ">=8" 1452 + } 1453 + }, 1454 + "node_modules/iceberg-js": { 1455 + "version": "0.8.1", 1456 + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", 1457 + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", 1458 + "license": "MIT", 1459 + "engines": { 1460 + "node": ">=20.0.0" 1461 + } 1462 + }, 1463 + "node_modules/iconv-lite": { 1464 + "version": "0.6.3", 1465 + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 1466 + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 1467 + "license": "MIT", 1468 + "dependencies": { 1469 + "safer-buffer": ">= 2.1.2 < 3.0.0" 1470 + }, 1471 + "engines": { 1472 + "node": ">=0.10.0" 1473 + } 1474 + }, 1475 + "node_modules/internmap": { 1476 + "version": "2.0.3", 1477 + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", 1478 + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", 1479 + "license": "ISC", 1480 + "engines": { 1481 + "node": ">=12" 1482 + } 1483 + }, 1484 + "node_modules/interval-tree-1d": { 1485 + "version": "1.0.4", 1486 + "resolved": "https://registry.npmjs.org/interval-tree-1d/-/interval-tree-1d-1.0.4.tgz", 1487 + "integrity": "sha512-wY8QJH+6wNI0uh4pDQzMvl+478Qh7Rl4qLmqiluxALlNvl+I+o5x38Pw3/z7mDPTPS1dQalZJXsmbvxx5gclhQ==", 1488 + "license": "MIT", 1489 + "dependencies": { 1490 + "binary-search-bounds": "^2.0.0" 1491 + } 1492 + }, 1493 + "node_modules/isoformat": { 1494 + "version": "0.2.1", 1495 + "resolved": "https://registry.npmjs.org/isoformat/-/isoformat-0.2.1.tgz", 1496 + "integrity": "sha512-tFLRAygk9NqrRPhJSnNGh7g7oaVWDwR0wKh/GM2LgmPa50Eg4UfyaCO4I8k6EqJHl1/uh2RAD6g06n5ygEnrjQ==", 1497 + "license": "ISC" 1498 + }, 1499 + "node_modules/json-bignum": { 1500 + "version": "0.0.3", 1501 + "resolved": "https://registry.npmjs.org/json-bignum/-/json-bignum-0.0.3.tgz", 1502 + "integrity": "sha512-2WHyXj3OfHSgNyuzDbSxI1w2jgw5gkWSWhS7Qg4bWXx1nLk3jnbwfUeS0PSba3IzpTUWdHxBieELUzXRjQB2zg==", 1503 + "engines": { 1504 + "node": ">=0.8" 1505 + } 1506 + }, 1507 + "node_modules/lightningcss": { 1508 + "version": "1.32.0", 1509 + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", 1510 + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", 1511 + "dev": true, 1512 + "license": "MPL-2.0", 1513 + "dependencies": { 1514 + "detect-libc": "^2.0.3" 1515 + }, 1516 + "engines": { 1517 + "node": ">= 12.0.0" 1518 + }, 1519 + "funding": { 1520 + "type": "opencollective", 1521 + "url": "https://opencollective.com/parcel" 1522 + }, 1523 + "optionalDependencies": { 1524 + "lightningcss-android-arm64": "1.32.0", 1525 + "lightningcss-darwin-arm64": "1.32.0", 1526 + "lightningcss-darwin-x64": "1.32.0", 1527 + "lightningcss-freebsd-x64": "1.32.0", 1528 + "lightningcss-linux-arm-gnueabihf": "1.32.0", 1529 + "lightningcss-linux-arm64-gnu": "1.32.0", 1530 + "lightningcss-linux-arm64-musl": "1.32.0", 1531 + "lightningcss-linux-x64-gnu": "1.32.0", 1532 + "lightningcss-linux-x64-musl": "1.32.0", 1533 + "lightningcss-win32-arm64-msvc": "1.32.0", 1534 + "lightningcss-win32-x64-msvc": "1.32.0" 1535 + } 1536 + }, 1537 + "node_modules/lightningcss-android-arm64": { 1538 + "version": "1.32.0", 1539 + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", 1540 + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", 1541 + "cpu": [ 1542 + "arm64" 1543 + ], 1544 + "dev": true, 1545 + "license": "MPL-2.0", 1546 + "optional": true, 1547 + "os": [ 1548 + "android" 1549 + ], 1550 + "engines": { 1551 + "node": ">= 12.0.0" 1552 + }, 1553 + "funding": { 1554 + "type": "opencollective", 1555 + "url": "https://opencollective.com/parcel" 1556 + } 1557 + }, 1558 + "node_modules/lightningcss-darwin-arm64": { 1559 + "version": "1.32.0", 1560 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", 1561 + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", 1562 + "cpu": [ 1563 + "arm64" 1564 + ], 1565 + "dev": true, 1566 + "license": "MPL-2.0", 1567 + "optional": true, 1568 + "os": [ 1569 + "darwin" 1570 + ], 1571 + "engines": { 1572 + "node": ">= 12.0.0" 1573 + }, 1574 + "funding": { 1575 + "type": "opencollective", 1576 + "url": "https://opencollective.com/parcel" 1577 + } 1578 + }, 1579 + "node_modules/lightningcss-darwin-x64": { 1580 + "version": "1.32.0", 1581 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", 1582 + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", 1583 + "cpu": [ 1584 + "x64" 1585 + ], 1586 + "dev": true, 1587 + "license": "MPL-2.0", 1588 + "optional": true, 1589 + "os": [ 1590 + "darwin" 1591 + ], 1592 + "engines": { 1593 + "node": ">= 12.0.0" 1594 + }, 1595 + "funding": { 1596 + "type": "opencollective", 1597 + "url": "https://opencollective.com/parcel" 1598 + } 1599 + }, 1600 + "node_modules/lightningcss-freebsd-x64": { 1601 + "version": "1.32.0", 1602 + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", 1603 + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", 1604 + "cpu": [ 1605 + "x64" 1606 + ], 1607 + "dev": true, 1608 + "license": "MPL-2.0", 1609 + "optional": true, 1610 + "os": [ 1611 + "freebsd" 1612 + ], 1613 + "engines": { 1614 + "node": ">= 12.0.0" 1615 + }, 1616 + "funding": { 1617 + "type": "opencollective", 1618 + "url": "https://opencollective.com/parcel" 1619 + } 1620 + }, 1621 + "node_modules/lightningcss-linux-arm-gnueabihf": { 1622 + "version": "1.32.0", 1623 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", 1624 + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", 1625 + "cpu": [ 1626 + "arm" 1627 + ], 1628 + "dev": true, 1629 + "license": "MPL-2.0", 1630 + "optional": true, 1631 + "os": [ 1632 + "linux" 1633 + ], 1634 + "engines": { 1635 + "node": ">= 12.0.0" 1636 + }, 1637 + "funding": { 1638 + "type": "opencollective", 1639 + "url": "https://opencollective.com/parcel" 1640 + } 1641 + }, 1642 + "node_modules/lightningcss-linux-arm64-gnu": { 1643 + "version": "1.32.0", 1644 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", 1645 + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", 1646 + "cpu": [ 1647 + "arm64" 1648 + ], 1649 + "dev": true, 1650 + "libc": [ 1651 + "glibc" 1652 + ], 1653 + "license": "MPL-2.0", 1654 + "optional": true, 1655 + "os": [ 1656 + "linux" 1657 + ], 1658 + "engines": { 1659 + "node": ">= 12.0.0" 1660 + }, 1661 + "funding": { 1662 + "type": "opencollective", 1663 + "url": "https://opencollective.com/parcel" 1664 + } 1665 + }, 1666 + "node_modules/lightningcss-linux-arm64-musl": { 1667 + "version": "1.32.0", 1668 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", 1669 + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", 1670 + "cpu": [ 1671 + "arm64" 1672 + ], 1673 + "dev": true, 1674 + "libc": [ 1675 + "musl" 1676 + ], 1677 + "license": "MPL-2.0", 1678 + "optional": true, 1679 + "os": [ 1680 + "linux" 1681 + ], 1682 + "engines": { 1683 + "node": ">= 12.0.0" 1684 + }, 1685 + "funding": { 1686 + "type": "opencollective", 1687 + "url": "https://opencollective.com/parcel" 1688 + } 1689 + }, 1690 + "node_modules/lightningcss-linux-x64-gnu": { 1691 + "version": "1.32.0", 1692 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", 1693 + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", 1694 + "cpu": [ 1695 + "x64" 1696 + ], 1697 + "dev": true, 1698 + "libc": [ 1699 + "glibc" 1700 + ], 1701 + "license": "MPL-2.0", 1702 + "optional": true, 1703 + "os": [ 1704 + "linux" 1705 + ], 1706 + "engines": { 1707 + "node": ">= 12.0.0" 1708 + }, 1709 + "funding": { 1710 + "type": "opencollective", 1711 + "url": "https://opencollective.com/parcel" 1712 + } 1713 + }, 1714 + "node_modules/lightningcss-linux-x64-musl": { 1715 + "version": "1.32.0", 1716 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", 1717 + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", 1718 + "cpu": [ 1719 + "x64" 1720 + ], 1721 + "dev": true, 1722 + "libc": [ 1723 + "musl" 1724 + ], 1725 + "license": "MPL-2.0", 1726 + "optional": true, 1727 + "os": [ 1728 + "linux" 1729 + ], 1730 + "engines": { 1731 + "node": ">= 12.0.0" 1732 + }, 1733 + "funding": { 1734 + "type": "opencollective", 1735 + "url": "https://opencollective.com/parcel" 1736 + } 1737 + }, 1738 + "node_modules/lightningcss-win32-arm64-msvc": { 1739 + "version": "1.32.0", 1740 + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", 1741 + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", 1742 + "cpu": [ 1743 + "arm64" 1744 + ], 1745 + "dev": true, 1746 + "license": "MPL-2.0", 1747 + "optional": true, 1748 + "os": [ 1749 + "win32" 1750 + ], 1751 + "engines": { 1752 + "node": ">= 12.0.0" 1753 + }, 1754 + "funding": { 1755 + "type": "opencollective", 1756 + "url": "https://opencollective.com/parcel" 1757 + } 1758 + }, 1759 + "node_modules/lightningcss-win32-x64-msvc": { 1760 + "version": "1.32.0", 1761 + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", 1762 + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", 1763 + "cpu": [ 1764 + "x64" 1765 + ], 1766 + "dev": true, 1767 + "license": "MPL-2.0", 1768 + "optional": true, 1769 + "os": [ 1770 + "win32" 1771 + ], 1772 + "engines": { 1773 + "node": ">= 12.0.0" 1774 + }, 1775 + "funding": { 1776 + "type": "opencollective", 1777 + "url": "https://opencollective.com/parcel" 1778 + } 1779 + }, 1780 + "node_modules/lodash.camelcase": { 1781 + "version": "4.3.0", 1782 + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", 1783 + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", 1784 + "license": "MIT" 1785 + }, 1786 + "node_modules/nanoid": { 1787 + "version": "3.3.12", 1788 + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", 1789 + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", 1790 + "dev": true, 1791 + "funding": [ 1792 + { 1793 + "type": "github", 1794 + "url": "https://github.com/sponsors/ai" 1795 + } 1796 + ], 1797 + "license": "MIT", 1798 + "bin": { 1799 + "nanoid": "bin/nanoid.cjs" 1800 + }, 1801 + "engines": { 1802 + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1803 + } 1804 + }, 1805 + "node_modules/picocolors": { 1806 + "version": "1.1.1", 1807 + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 1808 + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 1809 + "dev": true, 1810 + "license": "ISC" 1811 + }, 1812 + "node_modules/picomatch": { 1813 + "version": "4.0.4", 1814 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", 1815 + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", 1816 + "dev": true, 1817 + "license": "MIT", 1818 + "engines": { 1819 + "node": ">=12" 1820 + }, 1821 + "funding": { 1822 + "url": "https://github.com/sponsors/jonschlinkert" 1823 + } 1824 + }, 1825 + "node_modules/postcss": { 1826 + "version": "8.5.13", 1827 + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz", 1828 + "integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==", 1829 + "dev": true, 1830 + "funding": [ 1831 + { 1832 + "type": "opencollective", 1833 + "url": "https://opencollective.com/postcss/" 1834 + }, 1835 + { 1836 + "type": "tidelift", 1837 + "url": "https://tidelift.com/funding/github/npm/postcss" 1838 + }, 1839 + { 1840 + "type": "github", 1841 + "url": "https://github.com/sponsors/ai" 1842 + } 1843 + ], 1844 + "license": "MIT", 1845 + "dependencies": { 1846 + "nanoid": "^3.3.11", 1847 + "picocolors": "^1.1.1", 1848 + "source-map-js": "^1.2.1" 1849 + }, 1850 + "engines": { 1851 + "node": "^10 || ^12 || >=14" 1852 + } 1853 + }, 1854 + "node_modules/random": { 1855 + "version": "4.1.0", 1856 + "resolved": "https://registry.npmjs.org/random/-/random-4.1.0.tgz", 1857 + "integrity": "sha512-6Ajb7XmMSE9EFAMGC3kg9mvE7fGlBip25mYYuSMzw/uUSrmGilvZo2qwX3RnTRjwXkwkS+4swse9otZ92VjAtQ==", 1858 + "license": "MIT", 1859 + "dependencies": { 1860 + "seedrandom": "^3.0.5" 1861 + }, 1862 + "engines": { 1863 + "node": ">=14" 1864 + } 1865 + }, 1866 + "node_modules/robust-predicates": { 1867 + "version": "3.0.3", 1868 + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", 1869 + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", 1870 + "license": "Unlicense" 1871 + }, 1872 + "node_modules/rolldown": { 1873 + "version": "1.0.0-rc.17", 1874 + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", 1875 + "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", 1876 + "dev": true, 1877 + "license": "MIT", 1878 + "dependencies": { 1879 + "@oxc-project/types": "=0.127.0", 1880 + "@rolldown/pluginutils": "1.0.0-rc.17" 1881 + }, 1882 + "bin": { 1883 + "rolldown": "bin/cli.mjs" 1884 + }, 1885 + "engines": { 1886 + "node": "^20.19.0 || >=22.12.0" 1887 + }, 1888 + "optionalDependencies": { 1889 + "@rolldown/binding-android-arm64": "1.0.0-rc.17", 1890 + "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", 1891 + "@rolldown/binding-darwin-x64": "1.0.0-rc.17", 1892 + "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", 1893 + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", 1894 + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", 1895 + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", 1896 + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", 1897 + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", 1898 + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", 1899 + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", 1900 + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", 1901 + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", 1902 + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", 1903 + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" 1904 + } 1905 + }, 1906 + "node_modules/rw": { 1907 + "version": "1.3.3", 1908 + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", 1909 + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", 1910 + "license": "BSD-3-Clause" 1911 + }, 1912 + "node_modules/safer-buffer": { 1913 + "version": "2.1.2", 1914 + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1915 + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1916 + "license": "MIT" 1917 + }, 1918 + "node_modules/seedrandom": { 1919 + "version": "3.0.5", 1920 + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", 1921 + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", 1922 + "license": "MIT" 1923 + }, 1924 + "node_modules/source-map-js": { 1925 + "version": "1.2.1", 1926 + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 1927 + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 1928 + "dev": true, 1929 + "license": "BSD-3-Clause", 1930 + "engines": { 1931 + "node": ">=0.10.0" 1932 + } 1933 + }, 1934 + "node_modules/supports-color": { 1935 + "version": "7.2.0", 1936 + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 1937 + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 1938 + "license": "MIT", 1939 + "dependencies": { 1940 + "has-flag": "^4.0.0" 1941 + }, 1942 + "engines": { 1943 + "node": ">=8" 1944 + } 1945 + }, 1946 + "node_modules/table-layout": { 1947 + "version": "4.1.1", 1948 + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz", 1949 + "integrity": "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==", 1950 + "license": "MIT", 1951 + "dependencies": { 1952 + "array-back": "^6.2.2", 1953 + "wordwrapjs": "^5.1.0" 1954 + }, 1955 + "engines": { 1956 + "node": ">=12.17" 1957 + } 1958 + }, 1959 + "node_modules/table-layout/node_modules/array-back": { 1960 + "version": "6.2.3", 1961 + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.3.tgz", 1962 + "integrity": "sha512-SGDvmg6QTYiTxCBkYVmThcoa67uLl35pyzRHdpCGBOcqFy6BtwnphoFPk7LhJshD+Yk1Kt35WGWeZPTgwR4Fhw==", 1963 + "license": "MIT", 1964 + "engines": { 1965 + "node": ">=12.17" 1966 + } 1967 + }, 1968 + "node_modules/tinyglobby": { 1969 + "version": "0.2.16", 1970 + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", 1971 + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", 1972 + "dev": true, 1973 + "license": "MIT", 1974 + "dependencies": { 1975 + "fdir": "^6.5.0", 1976 + "picomatch": "^4.0.4" 1977 + }, 1978 + "engines": { 1979 + "node": ">=12.0.0" 1980 + }, 1981 + "funding": { 1982 + "url": "https://github.com/sponsors/SuperchupuDev" 1983 + } 1984 + }, 1985 + "node_modules/tslib": { 1986 + "version": "2.8.1", 1987 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 1988 + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 1989 + "license": "0BSD" 1990 + }, 1991 + "node_modules/typical": { 1992 + "version": "4.0.0", 1993 + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", 1994 + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", 1995 + "license": "MIT", 1996 + "engines": { 1997 + "node": ">=8" 1998 + } 1999 + }, 2000 + "node_modules/undici-types": { 2001 + "version": "7.19.2", 2002 + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", 2003 + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", 2004 + "license": "MIT" 2005 + }, 2006 + "node_modules/vite": { 2007 + "version": "8.0.10", 2008 + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", 2009 + "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", 2010 + "dev": true, 2011 + "license": "MIT", 2012 + "dependencies": { 2013 + "lightningcss": "^1.32.0", 2014 + "picomatch": "^4.0.4", 2015 + "postcss": "^8.5.10", 2016 + "rolldown": "1.0.0-rc.17", 2017 + "tinyglobby": "^0.2.16" 2018 + }, 2019 + "bin": { 2020 + "vite": "bin/vite.js" 2021 + }, 2022 + "engines": { 2023 + "node": "^20.19.0 || >=22.12.0" 2024 + }, 2025 + "funding": { 2026 + "url": "https://github.com/vitejs/vite?sponsor=1" 2027 + }, 2028 + "optionalDependencies": { 2029 + "fsevents": "~2.3.3" 2030 + }, 2031 + "peerDependencies": { 2032 + "@types/node": "^20.19.0 || >=22.12.0", 2033 + "@vitejs/devtools": "^0.1.0", 2034 + "esbuild": "^0.27.0 || ^0.28.0", 2035 + "jiti": ">=1.21.0", 2036 + "less": "^4.0.0", 2037 + "sass": "^1.70.0", 2038 + "sass-embedded": "^1.70.0", 2039 + "stylus": ">=0.54.8", 2040 + "sugarss": "^5.0.0", 2041 + "terser": "^5.16.0", 2042 + "tsx": "^4.8.1", 2043 + "yaml": "^2.4.2" 2044 + }, 2045 + "peerDependenciesMeta": { 2046 + "@types/node": { 2047 + "optional": true 2048 + }, 2049 + "@vitejs/devtools": { 2050 + "optional": true 2051 + }, 2052 + "esbuild": { 2053 + "optional": true 2054 + }, 2055 + "jiti": { 2056 + "optional": true 2057 + }, 2058 + "less": { 2059 + "optional": true 2060 + }, 2061 + "sass": { 2062 + "optional": true 2063 + }, 2064 + "sass-embedded": { 2065 + "optional": true 2066 + }, 2067 + "stylus": { 2068 + "optional": true 2069 + }, 2070 + "sugarss": { 2071 + "optional": true 2072 + }, 2073 + "terser": { 2074 + "optional": true 2075 + }, 2076 + "tsx": { 2077 + "optional": true 2078 + }, 2079 + "yaml": { 2080 + "optional": true 2081 + } 2082 + } 2083 + }, 2084 + "node_modules/wgsl_reflect": { 2085 + "version": "1.2.3", 2086 + "resolved": "https://registry.npmjs.org/wgsl_reflect/-/wgsl_reflect-1.2.3.tgz", 2087 + "integrity": "sha512-BQWBIsOn411M+ffBxmA6QRLvAOVbuz3Uk4NusxnqC1H7aeQcVLhdA3k2k/EFFFtqVjhz3z7JOOZF1a9hj2tv4Q==", 2088 + "license": "MIT" 2089 + }, 2090 + "node_modules/wordwrapjs": { 2091 + "version": "5.1.1", 2092 + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-5.1.1.tgz", 2093 + "integrity": "sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg==", 2094 + "license": "MIT", 2095 + "engines": { 2096 + "node": ">=12.17" 2097 + } 2098 + }, 2099 + "node_modules/ws": { 2100 + "version": "8.20.0", 2101 + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", 2102 + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", 2103 + "license": "MIT", 2104 + "engines": { 2105 + "node": ">=10.0.0" 2106 + }, 2107 + "peerDependencies": { 2108 + "bufferutil": "^4.0.1", 2109 + "utf-8-validate": ">=5.0.2" 2110 + }, 2111 + "peerDependenciesMeta": { 2112 + "bufferutil": { 2113 + "optional": true 2114 + }, 2115 + "utf-8-validate": { 2116 + "optional": true 2117 + } 2118 + } 2119 + } 2120 + } 2121 + }
+23
package.json
··· 1 + { 2 + "name": "tangle-of-trust", 3 + "version": "1.0.0", 4 + "description": "![screenshot of the app](https://l4.dunkirk.sh/i/f7XrvO_YwM2a.webp)", 5 + "type": "module", 6 + "scripts": { 7 + "build": "vite build", 8 + "dev": "vite dev" 9 + }, 10 + "repository": { 11 + "type": "git", 12 + "url": "git@knot.dunkirk.sh:did:plc:krxbvxvis5skq7jj6eot23ul/tangle-of-trust" 13 + }, 14 + "keywords": [], 15 + "author": "", 16 + "license": "ISC", 17 + "devDependencies": { 18 + "vite": "^8.0.10" 19 + }, 20 + "dependencies": { 21 + "@cosmograph/cosmograph": "^2.3.1" 22 + } 23 + }
+22
vite.config.js
··· 1 + import { defineConfig } from 'vite'; 2 + import path from 'path'; 3 + 4 + export default defineConfig({ 5 + root: 'web', 6 + resolve: { 7 + alias: { 8 + 'gl-bench': path.resolve(__dirname, 'node_modules/gl-bench/dist/gl-bench.module.js'), 9 + '@cosmograph/cosmograph/data-kit': path.resolve(__dirname, 'node_modules/@cosmograph/cosmograph/data-kit/data-kit.js'), 10 + }, 11 + }, 12 + build: { 13 + outDir: '../web-dist', 14 + emptyOutDir: true, 15 + }, 16 + server: { 17 + port: 5173, 18 + proxy: { 19 + '/api': 'http://localhost:8080', 20 + }, 21 + }, 22 + });
+2 -818
web/index.html
··· 7 7 <link rel="preconnect" href="https://fonts.googleapis.com"> 8 8 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 9 9 <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> 10 - <style> 11 - :root { 12 - --bg-page: #f1f5f9; 13 - --bg-card: #ffffff; 14 - --bg-input: #f9fafb; 15 - --text-primary: #000000; 16 - --text-secondary: #4b5563; 17 - --text-muted: #6b7280; 18 - --border-default: #e5e7eb; 19 - --border-strong: #d1d5db; 20 - --vouch: #40a02b; 21 - --vouch-edge: rgba(64,160,43,0.6); 22 - --denounce: #d20f39; 23 - --denounce-edge: rgba(210,15,57,0.6); 24 - --follow: #8839ef; 25 - --follow-edge: rgba(136,57,239,0.55); 26 - } 27 - 28 - @media (prefers-color-scheme: dark) { 29 - :root { 30 - --bg-page: #111827; 31 - --bg-card: #1f2937; 32 - --bg-input: #1f2937; 33 - --text-primary: #ffffff; 34 - --text-secondary: #d1d5db; 35 - --text-muted: #9ca3af; 36 - --border-default: #374151; 37 - --border-strong: #4b5563; 38 - --vouch: #a6da95; 39 - --vouch-edge: rgba(166,218,149,0.6); 40 - --denounce: #ed8796; 41 - --denounce-edge: rgba(237,135,150,0.6); 42 - --follow: #c6a0f6; 43 - --follow-edge: rgba(198,160,246,0.55); 44 - } 45 - } 46 - 47 - * { margin: 0; padding: 0; box-sizing: border-box; } 48 - html, body { height: 100%; overflow: hidden; } 49 - html { font-size: 14px; } 50 - 51 - body { 52 - font-family: 'Inter', system-ui, -apple-system, sans-serif; 53 - background: var(--bg-page); 54 - color: var(--text-primary); 55 - display: flex; 56 - flex-direction: column; 57 - transition: background-color 0.2s, color 0.2s; 58 - } 59 - 60 - header { 61 - flex-shrink: 0; 62 - background: var(--bg-card); 63 - border-bottom: 1px solid var(--border-default); 64 - padding: 0.5rem 1.5rem; 65 - display: flex; 66 - justify-content: space-between; 67 - align-items: center; 68 - z-index: 20; 69 - } 70 - 71 - .logo { 72 - display: flex; 73 - align-items: center; 74 - gap: 0.5rem; 75 - text-decoration: none; 76 - color: var(--text-primary); 77 - } 78 - .logo svg { width: 28px; height: 28px; } 79 - .logo-text { font-weight: 700; font-size: 1.25rem; } 80 - .logo-badge { 81 - font-weight: 500; 82 - font-size: 0.6875rem; 83 - padding: 0.125rem 0.375rem; 84 - border-radius: 0.25rem; 85 - background: var(--bg-input); 86 - border: 1px solid var(--border-default); 87 - } 88 - 89 - header nav { 90 - display: flex; 91 - align-items: center; 92 - gap: 1rem; 93 - font-size: 0.875rem; 94 - font-family: 'IBM Plex Mono', ui-monospace, monospace; 95 - color: var(--text-muted); 96 - } 97 - 98 - #search-wrap { 99 - position: relative; 100 - } 101 - #search-input { 102 - font-family: 'IBM Plex Mono', ui-monospace, monospace; 103 - font-size: 0.8125rem; 104 - padding: 0.25rem 0.5rem; 105 - border: 1px solid var(--border-default); 106 - border-radius: 0.25rem; 107 - background: var(--bg-input); 108 - color: var(--text-primary); 109 - outline: none; 110 - width: 200px; 111 - box-shadow: inset 0 -2px 0 0 rgba(0,0,0,0.05); 112 - transition: border-color 0.15s, box-shadow 0.15s; 113 - } 114 - #search-input:focus { 115 - border-color: var(--border-strong); 116 - box-shadow: inset 0 -2px 0 0 rgba(0,0,0,0.1), 0 0 0 2px rgba(136,57,239,0.15); 117 - } 118 - #search-dropdown { 119 - position: absolute; 120 - top: 100%; 121 - left: 0; 122 - margin-top: 4px; 123 - background: var(--bg-card); 124 - border: 1px solid var(--border-default); 125 - border-radius: 0.25rem; 126 - box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); 127 - z-index: 30; 128 - display: none; 129 - min-width: 240px; 130 - max-height: 260px; 131 - overflow-y: auto; 132 - } 133 - .search-item { 134 - display: flex; 135 - align-items: center; 136 - gap: 0.5rem; 137 - padding: 0.375rem 0.5rem; 138 - cursor: pointer; 139 - transition: background 0.1s; 140 - } 141 - .search-item:hover { background: var(--bg-input); } 142 - .search-item img { 143 - width: 24px; height: 24px; border-radius: 50%; 144 - object-fit: cover; flex-shrink: 0; 145 - border: 1px solid var(--border-default); 146 - } 147 - .search-item .search-fallback { 148 - width: 24px; height: 24px; border-radius: 50%; 149 - background: var(--bg-input); border: 1px solid var(--border-default); 150 - display: flex; align-items: center; justify-content: center; 151 - font-size: 0.625rem; color: var(--text-muted); flex-shrink: 0; 152 - } 153 - .search-item .search-handle { 154 - font-size: 0.8125rem; color: var(--text-primary); font-weight: 500; 155 - } 156 - .search-item .search-did { 157 - font-size: 0.6875rem; color: var(--text-muted); 158 - } 159 - .search-item .search-info { overflow: hidden; } 160 - 161 - #graph-container { flex: 1; min-height: 0; overflow: hidden; } 162 - 163 - #controls { 164 - flex-shrink: 0; 165 - background: var(--bg-card); 166 - border-top: 1px solid var(--border-default); 167 - padding: 0.5rem 1.5rem; 168 - z-index: 20; 169 - } 170 - 171 - #controls-row { 172 - display: flex; 173 - align-items: center; 174 - gap: 0.5rem; 175 - flex-wrap: wrap; 176 - } 177 - 178 - .filter-btn { 179 - position: relative; 180 - z-index: 1; 181 - display: inline-flex; 182 - min-height: 30px; 183 - align-items: center; 184 - justify-content: center; 185 - padding: 0 0.5rem 0.2rem; 186 - font-size: 0.875rem; 187 - font-family: inherit; 188 - color: var(--text-muted); 189 - cursor: pointer; 190 - background: transparent; 191 - border: none; 192 - } 193 - .filter-btn::before { 194 - content: ''; 195 - position: absolute; 196 - inset: 0; 197 - z-index: -1; 198 - display: block; 199 - border-radius: 0.25rem; 200 - border: 1px solid var(--border-default); 201 - background: var(--bg-card); 202 - box-shadow: inset 0 -2px 0 0 rgba(0,0,0,0.1), 0 1px 0 0 rgba(0,0,0,0.04); 203 - transition: all 0.15s ease-in-out; 204 - } 205 - .filter-btn:hover::before { 206 - box-shadow: inset 0 -2px 0 0 rgba(0,0,0,0.15), 0 2px 1px 0 rgba(0,0,0,0.06); 207 - background: var(--bg-input); 208 - } 209 - .filter-btn:active::before { box-shadow: inset 0 2px 2px 0 rgba(0,0,0,0.1); } 210 - .filter-btn.active { color: var(--text-primary); } 211 - .filter-btn.active .filter-dot { box-shadow: 0 0 0 2px var(--bg-card), 0 0 0 3px currentColor; } 212 - .filter-btn:not(.active) .filter-dot { opacity: 0.4; } 213 - .filter-btn:not(.active)::before { box-shadow: none; background: var(--bg-input); } 214 - 215 - .filter-dot { 216 - display: inline-block; 217 - width: 8px; height: 8px; 218 - border-radius: 50%; 219 - margin-right: 6px; 220 - transition: opacity 0.15s, box-shadow 0.15s; 221 - } 222 - 223 - .spacer { flex: 1; } 224 - 225 - .legend { display: none; } 226 - .legend-item { display: none; } 227 - .legend-dot { display: none; } 228 - 229 - #stats-row { 230 - display: flex; 231 - gap: 0; 232 - font-size: 0.75rem; 233 - font-family: 'IBM Plex Mono', ui-monospace, monospace; 234 - color: var(--text-muted); 235 - align-items: center; 236 - } 237 - #stats-row span { color: var(--text-secondary); margin-left: 4px; margin-right: 3px; } 238 - 239 - #loading { 240 - position: fixed; 241 - top: 50%; left: 50%; 242 - transform: translate(-50%, -50%); 243 - font-size: 0.875rem; 244 - color: var(--text-muted); 245 - z-index: 20; 246 - } 247 - 248 - #tooltip { 249 - position: fixed; 250 - background: var(--bg-card); 251 - border: 1px solid var(--border-default); 252 - padding: 0.5rem 0.75rem; 253 - border-radius: 0.25rem; 254 - font-size: 0.75rem; 255 - font-family: 'IBM Plex Mono', ui-monospace, monospace; 256 - pointer-events: none; 257 - z-index: 15; 258 - display: none; 259 - max-width: 320px; 260 - box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); 261 - } 262 - #tooltip .did { color: var(--text-primary); word-break: break-all; } 263 - .kv { color: var(--vouch); } 264 - .kd { color: var(--denounce); } 265 - .kf { color: var(--follow); } 266 - .ks { color: #df8e1d; } 267 - .action-line { display: flex; align-items: baseline; gap: 0; flex-wrap: wrap; } 268 - .action-line .action-word { font-weight: 600; } 269 - #tooltip .tooltip-profile { 270 - display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.375rem; 271 - } 272 - #tooltip .tooltip-avatar { 273 - width: 32px; height: 32px; border-radius: 50%; 274 - border: 2px solid var(--border-default); 275 - object-fit: cover; flex-shrink: 0; 276 - } 277 - #tooltip .tooltip-avatar-fallback { 278 - width: 32px; height: 32px; border-radius: 50%; 279 - background: var(--bg-input); border: 2px solid var(--border-default); 280 - display: flex; align-items: center; justify-content: center; 281 - font-size: 0.75rem; color: var(--text-muted); flex-shrink: 0; 282 - } 283 - #tooltip .tooltip-name { font-weight: 600; color: var(--text-primary); font-size: 0.8125rem; } 284 - #tooltip .tooltip-handle { color: var(--text-muted); font-size: 0.6875rem; } 285 - 286 - ::selection { background: rgba(250,204,21,0.3); color: #000; } 287 - @media (prefers-color-scheme: dark) { 288 - ::selection { background: rgba(161,98,7,0.5); color: #fff; } 289 - } 290 - </style> 291 10 </head> 292 11 <body> 293 12 <header> 294 13 <a href="/" class="logo"> 295 14 <svg viewBox="0 0 25 25" width="28" height="28" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> 296 - <path style="stroke-width:0.111183" d="m 16.775491,24.987061 c -0.78517,-0.0064 -1.384202,-0.234614 -2.033994,-0.631295 -0.931792,-0.490188 -1.643475,-1.31368 -2.152014,-2.221647 C 11.781409,23.136647 10.701392,23.744942 9.4922931,24.0886 8.9774725,24.238111 8.0757679,24.389777 6.5811304,23.84827 4.4270703,23.124679 2.8580086,20.883331 3.0363279,18.599583 3.0037061,17.652919 3.3488675,16.723769 3.8381157,15.925061 2.5329485,15.224503 1.4686756,14.048584 1.0611184,12.606459 0.81344502,11.816973 0.82385989,10.966486 0.91519098,10.154906 1.2422711,8.2387903 2.6795811,6.5725716 4.5299585,5.9732484 5.2685364,4.290122 6.8802592,3.0349975 8.706276,2.7794663 c 1.2124148,-0.1688264 2.46744,0.084987 3.52811,0.7011837 1.545426,-1.7139736 4.237779,-2.2205077 6.293579,-1.1676231 1.568222,0.7488935 2.689625,2.3113526 2.961888,4.0151464 1.492195,0.5977882 2.749007,1.8168898 3.242225,3.3644951 0.329805,0.9581836 0.340709,2.0135956 0.127128,2.9974286 -0.381606,1.535184 -1.465322,2.842146 -2.868035,3.556463 0.0034,0.273204 0.901506,2.243045 0.751284,3.729647 -0.03281,1.858525 -1.211631,3.619894 -2.846433,4.475452 -0.953967,0.556812 -2.084452,0.546309 -3.120531,0.535398 z m -4.470079,-5.349839 c 1.322246,-0.147248 2.189053,-1.300106 2.862307,-2.338363 0.318287,-0.472954 0.561404,-1.002348 0.803,-1.505815 0.313265,0.287151 0.578698,0.828085 1.074141,0.956909 0.521892,0.162542 1.133743,0.03052 1.45325,-0.443554 0.611414,-1.140449 0.31004,-2.516537 -0.04602,-3.698347 C 18.232844,11.92927 17.945151,11.232927 17.397785,10.751793 17.514522,9.9283111 17.026575,9.0919791 16.332883,8.6609491 15.741721,9.1323278 14.842258,9.1294949 14.271975,8.6252369 13.178927,9.7400102 12.177239,9.7029996 11.209704,8.8195135 10.992255,8.6209543 10.577326,10.031484 9.1211947,9.2324497 8.2846288,9.9333947 7.6359672,10.607693 7.0611981,11.578553 6.5026891,12.62523 5.9177873,13.554793 5.867393,14.69141 c -0.024234,0.66432 0.4948601,1.360337 1.1982269,1.306329 0.702996,0.06277 1.1815208,-0.629091 1.7138087,-0.916491 0.079382,0.927141 0.1688566,1.867012 0.5526926,2.714411 0.2185734,0.474191 0.6347468,0.870139 1.1430258,1.013693 0.394082,0.111489 0.828965,0.114989 1.205406,-0.05757 0.354889,-0.162414 0.623535,-0.476191 0.821879,-0.811434 0.375735,0.654705 0.892089,1.230083 1.519554,1.651602 0.342406,0.230165 0.740157,0.384163 1.15383,0.392604 0.471532,0.0096 0.936754,-0.167625 1.277957,-0.491679 0.185347,-0.175501 0.229756,-0.4427 0.379278,-0.647436 z"/> 15 + <path style="stroke-width:0.111183" d="m 16.775491,24.987061 c -0.78517,-0.0064 -1.384202,-0.234614 -2.033994,-0.631295 -0.931792,-0.490188 -1.643475,-1.31368 -2.152014,-2.221647 C 11.781409,23.136647 10.701392,23.744942 9.4922931,24.0886 8.9774725,24.238111 8.0757679,24.389777 6.5811304,23.84827 4.4270703,23.124679 2.8580086,20.883331 3.0363279,18.599583 3.0037061,17.652919 3.3488675,16.723769 3.8381157,15.925061 2.5329485,15.224503 1.4686756,14.048584 1.0611184,12.606459 0.81344502,11.816973 0.82385989,10.966486 0.91519098,10.154906 1.2422711,8.2387903 2.6795811,6.5725716 4.5299585,5.9732484 5.2685364,4.290122 6.8802592,3.0349975 8.706276,2.7794663 c 1.2124148,-0.1688264 2.46744,0.084987 3.52811,0.7011837 1.545426,-1.7139736 4.237779,-2.2205077 6.293579,-1.1676231 1.568222,0.7488935 2.689625,2.3113526 2.961888,4.0151464 1.492195,0.5977882 2.749007,1.8168898 3.242225,3.3644951 0.329805,0.9581836 0.340709,2.0135956 0.127128,2.9974286 -0.381606,1.535184 -1.465322,2.842146 -2.868035,3.556463 0.0034,0.273204 0.901506,2.243045 0.751284,3.729647 -0.03281,1.858525 -1.211631,3.619894 -2.846433,4.475452 -0.953967,0.556812 -2.084452,0.546309 -3.120531,0.535398 z m -4.470079,-5.349839 c 1.322246,-0.147248 2.189053,-1.300106 2.862307,-2.338363 0.318287,-0.472954 0.561404,-1.002348 0.803,-1.505815 0.313265,0.287151 0.578698,0.828085 1.074141,0.956909 0.521892,0.162542 1.133743,0.03052 1.45325,-0.443554 0.611414,-1.140449 0.31004,-2.516537 -0.04602,-3.698347 C 18.232844,11.92927 17.945151,11.232927 17.397785,10.751793 17.514522,9.9283111 17.026575,9.0919791 16.332883,8.6609491 15.741721,9.1323278 14.842258,9.1294949 14.271975,8.6252369 13.178927,9.7400102 12.177239,9.7029996 11.209704,8.8195135 10.992255,8.6209543 10.577326,10.031484 9.1211947,9.2324497 8.2846288,9.9333947 7.6359672,10.607693 7.0611981,11.578553 6.5026891,12.62523 5.9177873,13.554793 5.867393,14.69141 c -0.024234,0.66432 0.4948601,1.360337 1.1982269,1.306329 0.702996,0.06277 1.1815208,-0.629091 1"/> 297 16 </svg> 298 17 <span class="logo-text">tangled</span> 299 18 <span class="logo-badge">trust</span> ··· 336 55 </div> 337 56 </div> 338 57 339 - <script type="module"> 340 - import ForceGraph from 'https://esm.run/force-graph@1.43.5'; 341 - 342 - const container = document.getElementById('graph-container'); 343 - const loading = document.getElementById('loading'); 344 - const tooltip = document.getElementById('tooltip'); 345 - const headerStats = document.getElementById('header-stats'); 346 - 347 - let graphData = null; 348 - let profileMap = {}; 349 - let fg = null; 350 - const resolvingDIDs = new Set(); 351 - 352 - const edgeFilters = { 353 - 'vouch/vouch': true, 354 - 'vouch/denounce': true, 355 - 'vouch/mixed': true, 356 - 'follow': false, 357 - }; 358 - 359 - document.querySelectorAll('.filter-btn').forEach(btn => { 360 - btn.addEventListener('click', () => { 361 - const kind = btn.dataset.kind; 362 - edgeFilters[kind] = !edgeFilters[kind]; 363 - btn.classList.toggle('active', edgeFilters[kind]); 364 - refreshGraph(); 365 - }); 366 - }); 367 - 368 - function getCSS(v) { 369 - return getComputedStyle(document.documentElement).getPropertyValue(v).trim(); 370 - } 371 - 372 - function isDarkMode() { 373 - return window.matchMedia('(prefers-color-scheme: dark)').matches; 374 - } 375 - 376 - function shortenDID(did) { 377 - if (did.length > 24) return did.slice(0, 12) + '…' + did.slice(-8); 378 - return did; 379 - } 380 - 381 - async function resolveDID(did) { 382 - if (profileMap[did]?.handle) return profileMap[did]; 383 - if (resolvingDIDs.has(did)) return profileMap[did] || null; 384 - resolvingDIDs.add(did); 385 - try { 386 - const resp = await fetch(`/api/resolve?did=${encodeURIComponent(did)}`); 387 - if (resp.ok) { 388 - const p = await resp.json(); 389 - if (p.handle) { 390 - profileMap[did] = { handle: p.handle, avatar: p.avatar_url || p.avatar || '' }; 391 - return profileMap[did]; 392 - } 393 - } 394 - } catch {} 395 - return profileMap[did] || null; 396 - } 397 - 398 - async function loadData() { 399 - const resp = await fetch('/api/graph'); 400 - graphData = await resp.json(); 401 - 402 - if (!graphData.edges || graphData.edges.length === 0) { 403 - loading.textContent = 'No data yet. Run backfill or ingest to collect records...'; 404 - return; 405 - } 406 - 407 - for (const node of graphData.nodes) { 408 - if (node.handle || node.avatar) { 409 - profileMap[node.id] = { handle: node.handle || '', avatar: node.avatar || '' }; 410 - } 411 - } 412 - 413 - loading.style.display = 'none'; 414 - initGraph(); 415 - } 416 - 417 - let nodeDegrees = {}; 418 - let nodeInVouch = {}; 419 - let nodeInDenounce = {}; 420 - let nodeOutVouch = {}; 421 - let nodeOutDenounce = {}; 422 - let nodeColors = {}; 423 - 424 - function buildGraph() { 425 - if (!graphData) return { nodes: [], links: [] }; 426 - 427 - const nodeSet = new Set(); 428 - const links = []; 429 - 430 - // deduplicate mutual edges between same pair 431 - // for vouch/denounce: if A→B vouch and B→A denounce exist, merge into one line 432 - // for same-kind mutual: A→B vouch and B→A vouch -> one line with mutual=true 433 - const pairIndex = new Map(); // "min|max" -> { vouch: link, denounce: link } 434 - for (const edge of graphData.edges) { 435 - if (!edgeFilters[edge.kind]) continue; 436 - const min = edge.source < edge.target ? edge.source : edge.target; 437 - const max = edge.source < edge.target ? edge.target : edge.source; 438 - const pairKey = min + '|' + max; 439 - if (!pairIndex.has(pairKey)) pairIndex.set(pairKey, {}); 440 - const entry = pairIndex.get(pairKey); 441 - const kind = edge.kind === 'vouch/vouch' ? 'vouch' : edge.kind === 'vouch/denounce' ? 'denounce' : 'follow'; 442 - if (kind === 'vouch' || kind === 'denounce') { 443 - if (!entry[kind]) { 444 - entry[kind] = { 445 - source: edge.source, 446 - target: edge.target, 447 - kind: edge.kind, 448 - reason: edge.reason, 449 - time: edge.time, 450 - }; 451 - } else { 452 - // same kind, both directions -> mutual 453 - entry[kind].mutual = true; 454 - } 455 - } else { 456 - // follows: just add directly 457 - if (!entry.follows) entry.follows = []; 458 - entry.follows.push({ 459 - source: edge.source, 460 - target: edge.target, 461 - kind: edge.kind, 462 - reason: edge.reason, 463 - time: edge.time, 464 - mutual: false, 465 - }); 466 - } 467 - } 468 - 469 - for (const entry of pairIndex.values()) { 470 - if (entry.vouch && entry.denounce) { 471 - // mixed: one line showing both 472 - nodeSet.add(entry.vouch.source); 473 - nodeSet.add(entry.vouch.target); 474 - links.push({ 475 - source: entry.vouch.source, 476 - target: entry.vouch.target, 477 - kind: 'vouch/mixed', 478 - reason: entry.vouch.reason, 479 - time: entry.vouch.time, 480 - mutual: true, 481 - }); 482 - } else if (entry.vouch) { 483 - nodeSet.add(entry.vouch.source); 484 - nodeSet.add(entry.vouch.target); 485 - links.push(entry.vouch); 486 - } else if (entry.denounce) { 487 - nodeSet.add(entry.denounce.source); 488 - nodeSet.add(entry.denounce.target); 489 - links.push(entry.denounce); 490 - } 491 - if (entry.follows) { 492 - for (const fl of entry.follows) { 493 - nodeSet.add(fl.source); 494 - nodeSet.add(fl.target); 495 - links.push(fl); 496 - } 497 - } 498 - } 499 - 500 - const nodes = [...nodeSet].map(id => { 501 - const p = profileMap[id] || {}; 502 - return { id, label: p.handle || id, handle: p.handle || '', avatar: p.avatar || '' }; 503 - }); 504 - 505 - // pre-compute degrees and vouch/denounce counts 506 - nodeDegrees = {}; 507 - nodeInVouch = {}; 508 - nodeInDenounce = {}; 509 - nodeOutVouch = {}; 510 - nodeOutDenounce = {}; 511 - for (const n of nodes) { nodeDegrees[n.id] = 0; nodeInVouch[n.id] = 0; nodeInDenounce[n.id] = 0; nodeOutVouch[n.id] = 0; nodeOutDenounce[n.id] = 0; } 512 - for (const link of links) { 513 - const src = link.source.id || link.source; 514 - const tgt = link.target.id || link.target; 515 - nodeDegrees[src] = (nodeDegrees[src] || 0) + 1; 516 - nodeDegrees[tgt] = (nodeDegrees[tgt] || 0) + 1; 517 - if (link.kind === 'vouch/vouch') { nodeInVouch[tgt] = (nodeInVouch[tgt] || 0) + 1; nodeOutVouch[src] = (nodeOutVouch[src] || 0) + 1; } 518 - else if (link.kind === 'vouch/denounce') { nodeInDenounce[tgt] = (nodeInDenounce[tgt] || 0) + 1; nodeOutDenounce[src] = (nodeOutDenounce[src] || 0) + 1; } 519 - else if (link.kind === 'vouch/mixed') { nodeInVouch[tgt] = (nodeInVouch[tgt] || 0) + 1; nodeInDenounce[tgt] = (nodeInDenounce[tgt] || 0) + 1; nodeOutVouch[src] = (nodeOutVouch[src] || 0) + 1; nodeOutDenounce[src] = (nodeOutDenounce[src] || 0) + 1; } 520 - } 521 - 522 - // pre-compute node colors 523 - const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches; 524 - nodeColors = {}; 525 - for (const n of nodes) { 526 - const vouches = nodeInVouch[n.id] || 0; 527 - const denounces = nodeInDenounce[n.id] || 0; 528 - const total = vouches + denounces; 529 - if (total === 0) { nodeColors[n.id] = isDark ? '#6e738d' : '#9ca0b0'; continue; } 530 - const ratio = denounces / total; 531 - if (ratio > 0.5) nodeColors[n.id] = isDark ? '#ed8796' : '#d20f39'; 532 - else if (ratio > 0.1) nodeColors[n.id] = isDark ? '#eed49f' : '#df8e1d'; 533 - else nodeColors[n.id] = isDark ? '#a6da95' : '#40a02b'; 534 - } 535 - 536 - return { nodes, links }; 537 - } 538 - 539 - function edgeColor(link) { 540 - switch (link.kind) { 541 - case 'vouch/vouch': return getCSS('--vouch-edge') || 'rgba(64,160,43,0.6)'; 542 - case 'vouch/denounce': return getCSS('--denounce-edge') || 'rgba(210,15,57,0.6)'; 543 - case 'vouch/mixed': return isDarkMode() ? 'rgba(238,212,159,0.6)' : 'rgba(223,142,29,0.6)'; 544 - case 'follow': return getCSS('--follow-edge') || 'rgba(136,57,239,0.55)'; 545 - default: return 'rgba(107,114,128,0.3)'; 546 - } 547 - } 548 - 549 - function edgeWidth(link) { 550 - const base = (() => { 551 - switch (link.kind) { 552 - case 'vouch/vouch': return 2; 553 - case 'vouch/denounce': return 1.5; 554 - case 'vouch/mixed': return 2; 555 - case 'follow': return 1; 556 - default: return 1; 557 - } 558 - })(); 559 - return link.mutual ? base + 1 : base; 560 - } 561 - 562 - function arrowColor(link) { 563 - switch (link.kind) { 564 - case 'vouch/vouch': return getCSS('--vouch') || '#40a02b'; 565 - case 'vouch/denounce': return getCSS('--denounce') || '#d20f39'; 566 - case 'vouch/mixed': return isDarkMode() ? '#eed49f' : '#df8e1d'; 567 - case 'follow': return getCSS('--follow') || '#8839ef'; 568 - default: return '#6b7280'; 569 - } 570 - } 571 - 572 - function nodeColor(node) { 573 - return nodeColors[node.id] || (window.matchMedia('(prefers-color-scheme: dark)').matches ? '#6e738d' : '#9ca0b0'); 574 - } 575 - 576 - const avatarCache = new Map(); 577 - function getAvatarImg(url) { 578 - if (!url) return null; 579 - if (avatarCache.has(url)) return avatarCache.get(url); 580 - const proxyUrl = `/api/proxy/avatar?url=${encodeURIComponent(url)}`; 581 - const img = new Image(); 582 - img.crossOrigin = 'anonymous'; 583 - img.src = proxyUrl; 584 - avatarCache.set(url, img); 585 - return img; 586 - } 587 - 588 - function initGraph() { 589 - const data = buildGraph(); 590 - const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches; 591 - 592 - for (const node of data.nodes) { 593 - if (node.avatar) getAvatarImg(node.avatar); 594 - } 595 - 596 - fg = ForceGraph()(container) 597 - .graphData(data) 598 - .backgroundColor(isDark ? '#111827' : '#f1f5f9') 599 - .nodeLabel('') 600 - .nodeCanvasObject((node, ctx, globalScale) => { 601 - const size = Math.max(6, Math.min(20, 4 * Math.log2(getNodeDegree(node.id) + 2))); 602 - const img = node.avatar ? getAvatarImg(node.avatar) : null; 603 - 604 - if (img && img.complete && img.naturalWidth > 0) { 605 - ctx.save(); 606 - ctx.beginPath(); 607 - ctx.arc(node.x, node.y, size, 0, 2 * Math.PI); 608 - ctx.closePath(); 609 - ctx.clip(); 610 - ctx.drawImage(img, node.x - size, node.y - size, size * 2, size * 2); 611 - ctx.restore(); 612 - 613 - // colored ring matching node degree 614 - const ringColor = nodeColor(node); 615 - ctx.beginPath(); 616 - ctx.arc(node.x, node.y, size + 1.5, 0, 2 * Math.PI); 617 - ctx.strokeStyle = ringColor; 618 - ctx.lineWidth = 2; 619 - ctx.stroke(); 620 - } else { 621 - const color = nodeColor(node); 622 - ctx.beginPath(); 623 - ctx.arc(node.x, node.y, size, 0, 2 * Math.PI); 624 - ctx.fillStyle = isDark ? '#1f2937' : '#f1f5f9'; 625 - ctx.fill(); 626 - ctx.strokeStyle = color; 627 - ctx.lineWidth = 2; 628 - ctx.stroke(); 629 - 630 - const initial = (node.handle || node.id).charAt(0).toUpperCase(); 631 - const fontSize = Math.max(4, size * 0.9); 632 - ctx.font = `${fontSize}px Inter, system-ui, sans-serif`; 633 - ctx.textAlign = 'center'; 634 - ctx.textBaseline = 'middle'; 635 - ctx.fillStyle = color; 636 - ctx.fillText(initial, node.x, node.y); 637 - } 638 - }) 639 - .nodeCanvasObjectMode(() => 'replace') 640 - .nodeVal(node => Math.max(1, Math.log2(getNodeDegree(node.id) + 1))) 641 - .linkColor(edgeColor) 642 - .linkWidth(edgeWidth) 643 - .linkDirectionalArrowLength(link => link.kind.startsWith('vouch/') ? 4 : 3) 644 - .linkDirectionalArrowRelPos(1) 645 - .linkDirectionalArrowColor(arrowColor) 646 - .linkVisibility(link => edgeFilters[link.kind] !== false) 647 - .onNodeHover(async node => { 648 - if (node) { 649 - tooltip.style.display = 'block'; 650 - renderTooltip(node); 651 - // lazy resolve if missing profile 652 - if (!profileMap[node.id]?.handle) { 653 - const p = await resolveDID(node.id); 654 - if (p?.handle) { 655 - node.handle = p.handle; 656 - node.avatar = p.avatar; 657 - node.label = p.handle; 658 - if (p.avatar) getAvatarImg(p.avatar); 659 - renderTooltip(node); 660 - fg.nodeCanvasObject(scratchRedraw); 661 - } 662 - } 663 - } else { 664 - tooltip.style.display = 'none'; 665 - } 666 - }) 667 - .onLinkHover(link => { 668 - if (link) { 669 - const srcId = link.source.id || link.source; 670 - const tgtId = link.target.id || link.target; 671 - const srcP = profileMap[srcId] || {}; 672 - const tgtP = profileMap[tgtId] || {}; 673 - const kindClass = link.kind === 'vouch/denounce' ? 'kd' 674 - : link.kind === 'vouch/vouch' ? 'kv' 675 - : link.kind === 'vouch/mixed' ? 'ks' : 'kf'; 676 - const label = link.kind === 'vouch/denounce' ? 'denounce' 677 - : link.kind === 'vouch/vouch' ? 'vouch' 678 - : link.kind === 'vouch/mixed' ? 'mixed' : 'follow'; 679 - const mutualTag = link.mutual ? ' (mutual)' : ''; 680 - tooltip.style.display = 'block'; 681 - const srcAvatar = srcP.avatar 682 - ? `<img class="tooltip-avatar" src="/api/proxy/avatar?url=${encodeURIComponent(srcP.avatar)}" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"><div class="tooltip-avatar-fallback" style="display:none">${(srcP.handle || srcId).charAt(0).toUpperCase()}</div>` 683 - : `<div class="tooltip-avatar-fallback">${(srcP.handle || shortenDID(srcId)).charAt(0).toUpperCase()}</div>`; 684 - const tgtAvatar = tgtP.avatar 685 - ? `<img class="tooltip-avatar" src="/api/proxy/avatar?url=${encodeURIComponent(tgtP.avatar)}" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"><div class="tooltip-avatar-fallback" style="display:none">${(tgtP.handle || tgtId).charAt(0).toUpperCase()}</div>` 686 - : `<div class="tooltip-avatar-fallback">${(tgtP.handle || shortenDID(tgtId)).charAt(0).toUpperCase()}</div>`; 687 - tooltip.innerHTML = ` 688 - <div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap"> 689 - ${srcAvatar} 690 - <span class="did">${srcP.handle || shortenDID(srcId)}</span> 691 - <span class="${kindClass} action-word">${label}s${mutualTag}</span> 692 - ${tgtAvatar} 693 - <span class="did">${tgtP.handle || shortenDID(tgtId)}</span> 694 - </div> 695 - ${link.reason ? `<div style="margin-top:4px;color:var(--text-muted);word-break:break-word">${link.reason}</div>` : ''} 696 - `; 697 - } else { 698 - tooltip.style.display = 'none'; 699 - } 700 - }) 701 - .onNodeClick(node => { 702 - if (node) window.open(`https://tangled.org/${node.id}`, '_blank'); 703 - }) 704 - .cooldownTime(1500); 705 - 706 - // tune forces for better spacing 707 - fg.d3Force('charge').strength(node => { 708 - const deg = nodeDegrees[node.id] || 1; 709 - return -30 * Math.min(deg, 20); 710 - }); 711 - fg.d3Force('link').distance(link => link.kind === 'follow' ? 30 : 60); 712 - 713 - container.addEventListener('mousemove', e => { 714 - if (tooltip.style.display === 'block') { 715 - tooltip.style.left = (e.clientX + 14) + 'px'; 716 - tooltip.style.top = (e.clientY + 14) + 'px'; 717 - } 718 - }); 719 - 720 - document.addEventListener('keydown', e => { 721 - if (e.code === 'Space' && e.target === document.body) { 722 - e.preventDefault(); 723 - const controlsHeight = document.getElementById('controls').offsetHeight; 724 - fg.zoomToFit(400, 30); 725 - const c = fg.centerAt(); 726 - fg.centerAt(c.x, c.y + controlsHeight / 2, 0); 727 - } 728 - }); 729 - 730 - updateStats(data); 731 - } 732 - 733 - function renderTooltip(node) { 734 - const profile = profileMap[node.id] || {}; 735 - const handle = profile.handle || node.handle || ''; 736 - const avatar = profile.avatar || node.avatar || ''; 737 - const avatarHtml = avatar 738 - ? `<img class="tooltip-avatar" src="${avatar}" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"><div class="tooltip-avatar-fallback" style="display:none">${(handle || node.id).charAt(0).toUpperCase()}</div>` 739 - : `<div class="tooltip-avatar-fallback">${(handle || node.id).charAt(0).toUpperCase()}</div>`; 740 - 741 - // count incoming vouches vs denounces 742 - const inV = nodeInVouch[node.id] || 0; 743 - const inD = nodeInDenounce[node.id] || 0; 744 - const outV = nodeOutVouch[node.id] || 0; 745 - const outD = nodeOutDenounce[node.id] || 0; 746 - const total = inV + inD; 747 - let trustLine = ''; 748 - if (total > 0 || outV + outD > 0) { 749 - const pct = total > 0 ? Math.round((inV / total) * 100) : null; 750 - let pctStr; 751 - if (pct !== null) { 752 - const c = pct > 90 ? 'kv' : pct > 50 ? 'ks' : 'kd'; 753 - pctStr = `<span class="${c}">${pct}%</span>`; 754 - } else { 755 - pctStr = '<span style="color:var(--text-muted)">?</span>'; 756 - } 757 - const inPart = total > 0 ? `<span class="kv">${inV}v</span> <span class="kd">${inD}d</span> in` : ''; 758 - const outPart = outV + outD > 0 ? `<span class="kv">${outV}v</span> <span class="kd">${outD}d</span> out` : ''; 759 - const sep = inPart && outPart ? ' · ' : ''; 760 - trustLine = `<div style="margin-top:4px;font-size:0.6875rem">${pctStr} trusted · ${inPart}${sep}${outPart}</div>`; 761 - } 762 - 763 - tooltip.innerHTML = ` 764 - <div class="tooltip-profile"> 765 - ${avatarHtml} 766 - <div> 767 - <div class="tooltip-name">${handle || shortenDID(node.id)}</div> 768 - ${handle ? `<div class="tooltip-handle">${shortenDID(node.id)}</div>` : ''} 769 - ${trustLine} 770 - </div> 771 - </div> 772 - `; 773 - } 774 - 775 - // force a re-render after lazy resolution 776 - let scratchRedrawCount = 0; 777 - function scratchRedraw(node, ctx, gs) { 778 - // force-graph redraws on every frame so once profile lands 779 - // the next paint will pick up the avatar naturally 780 - } 781 - 782 - function getNodeDegree(nodeId) { 783 - return nodeDegrees[nodeId] || 0; 784 - } 785 - 786 - function refreshGraph() { 787 - if (!fg || !graphData) return; 788 - const data = buildGraph(); 789 - fg.graphData(data); 790 - updateStats(data); 791 - } 792 - 793 - function updateStats(data) { 794 - document.getElementById('stat-nodes').textContent = data.nodes.length; 795 - let vouches = 0, denounces = 0, mixed = 0, follows = 0; 796 - for (const l of data.links) { 797 - switch (l.kind) { 798 - case 'vouch/vouch': vouches++; break; 799 - case 'vouch/denounce': denounces++; break; 800 - case 'vouch/mixed': mixed++; break; 801 - case 'follow': follows++; break; 802 - } 803 - } 804 - document.getElementById('stat-vouches').textContent = vouches; 805 - document.getElementById('stat-denounces').textContent = denounces; 806 - document.getElementById('stat-mixed').textContent = mixed; 807 - document.getElementById('stat-follows').textContent = follows; 808 - headerStats.textContent = `${data.nodes.length} nodes / ${data.links.length} edges`; 809 - } 810 - 811 - // --- search --- 812 - const searchInput = document.getElementById('search-input'); 813 - const searchDropdown = document.getElementById('search-dropdown'); 814 - let searchTimeout = null; 815 - 816 - searchInput.addEventListener('input', () => { 817 - clearTimeout(searchTimeout); 818 - const q = searchInput.value.trim(); 819 - if (q.length < 2) { searchDropdown.style.display = 'none'; return; } 820 - searchTimeout = setTimeout(async () => { 821 - try { 822 - const resp = await fetch(`/api/search?q=${encodeURIComponent(q)}`); 823 - if (!resp.ok) return; 824 - const data = await resp.json(); 825 - const actors = data.actors || []; 826 - if (actors.length === 0) { searchDropdown.style.display = 'none'; return; } 827 - searchDropdown.innerHTML = actors.map(a => { 828 - const avatar = a.avatar 829 - ? `<img src="/api/proxy/avatar?url=${encodeURIComponent(a.avatar)}" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"><div class="search-fallback" style="display:none">${a.handle.charAt(0).toUpperCase()}</div>` 830 - : `<div class="search-fallback">${a.handle.charAt(0).toUpperCase()}</div>`; 831 - return `<div class="search-item" data-did="${a.did}"> 832 - ${avatar} 833 - <div class="search-info"> 834 - <div class="search-handle">${a.handle}</div> 835 - <div class="search-did">${shortenDID(a.did)}</div> 836 - </div> 837 - </div>`; 838 - }).join(''); 839 - searchDropdown.style.display = 'block'; 840 - } catch {} 841 - }, 250); 842 - }); 843 - 844 - searchDropdown.addEventListener('click', e => { 845 - const item = e.target.closest('.search-item'); 846 - if (!item) return; 847 - const did = item.dataset.did; 848 - searchInput.value = ''; 849 - searchDropdown.style.display = 'none'; 850 - // center on the node if it exists in the graph 851 - if (fg) { 852 - const node = fg.graphData().nodes.find(n => n.id === did); 853 - if (node && node.x != null) { 854 - fg.centerAt(node.x, node.y, 800); 855 - fg.zoom(3, 800); 856 - } 857 - } 858 - }); 859 - 860 - document.addEventListener('click', e => { 861 - if (!document.getElementById('search-wrap').contains(e.target)) { 862 - searchDropdown.style.display = 'none'; 863 - } 864 - }); 865 - 866 - searchInput.addEventListener('keydown', e => { 867 - if (e.key === 'Escape') { 868 - searchDropdown.style.display = 'none'; 869 - searchInput.blur(); 870 - } 871 - }); 872 - 873 - loadData(); 874 - </script> 58 + <script type="module" src="/main.js"></script> 875 59 </body> 876 60 </html>
+485
web/main.js
··· 1 + import { Cosmograph } from '@cosmograph/cosmograph'; 2 + import { prepareCosmographData } from '@cosmograph/cosmograph/data-kit'; 3 + import './style.css'; 4 + 5 + const container = document.getElementById('graph-container'); 6 + const loading = document.getElementById('loading'); 7 + const tooltip = document.getElementById('tooltip'); 8 + const headerStats = document.getElementById('header-stats'); 9 + const searchInput = document.getElementById('search-input'); 10 + const searchDropdown = document.getElementById('search-dropdown'); 11 + 12 + let graphData = null; 13 + let profileMap = {}; 14 + let cosmograph = null; 15 + let popup = null; 16 + let pointIdToIndex = {}; 17 + let resolvingDIDs = new Set(); 18 + let currentRawPoints = []; 19 + let currentLinks = []; 20 + let currentNodes = []; 21 + 22 + let nodeDegrees = {}; 23 + let nodeInVouch = {}; 24 + let nodeInDenounce = {}; 25 + let nodeOutVouch = {}; 26 + let nodeOutDenounce = {}; 27 + let nodeColors = {}; 28 + 29 + const edgeFilters = { 30 + 'vouch/vouch': true, 31 + 'vouch/denounce': true, 32 + 'vouch/mixed': true, 33 + 'follow': false, 34 + }; 35 + 36 + document.querySelectorAll('.filter-btn').forEach(btn => { 37 + btn.addEventListener('click', () => { 38 + const kind = btn.dataset.kind; 39 + edgeFilters[kind] = !edgeFilters[kind]; 40 + btn.classList.toggle('active', edgeFilters[kind]); 41 + rebuildGraph(); 42 + }); 43 + }); 44 + 45 + function isDarkMode() { 46 + return window.matchMedia('(prefers-color-scheme: dark)').matches; 47 + } 48 + 49 + function shortenDID(did) { 50 + if (did.length > 24) return did.slice(0, 12) + '…' + did.slice(-8); 51 + return did; 52 + } 53 + 54 + async function resolveDID(did) { 55 + if (profileMap[did]?.handle) return profileMap[did]; 56 + if (resolvingDIDs.has(did)) return profileMap[did] || null; 57 + resolvingDIDs.add(did); 58 + try { 59 + const resp = await fetch(`/api/resolve?did=${encodeURIComponent(did)}`); 60 + if (resp.ok) { 61 + const p = await resp.json(); 62 + if (p.handle) { 63 + profileMap[did] = { handle: p.handle, avatar: p.avatar_url || p.avatar || '' }; 64 + return profileMap[did]; 65 + } 66 + } 67 + } catch {} 68 + return profileMap[did] || null; 69 + } 70 + 71 + function buildGraph() { 72 + if (!graphData) return { points: [], links: [] }; 73 + 74 + const nodeSet = new Set(); 75 + const links = []; 76 + 77 + // deduplicate mutual edges between same pair 78 + const pairIndex = new Map(); 79 + for (const edge of graphData.edges) { 80 + if (!edgeFilters[edge.kind]) continue; 81 + const min = edge.source < edge.target ? edge.source : edge.target; 82 + const max = edge.source < edge.target ? edge.target : edge.source; 83 + const pairKey = min + '|' + max; 84 + if (!pairIndex.has(pairKey)) pairIndex.set(pairKey, {}); 85 + const entry = pairIndex.get(pairKey); 86 + const kind = edge.kind === 'vouch/vouch' ? 'vouch' : edge.kind === 'vouch/denounce' ? 'denounce' : 'follow'; 87 + if (kind === 'vouch' || kind === 'denounce') { 88 + if (!entry[kind]) { 89 + entry[kind] = { source: edge.source, target: edge.target, kind: edge.kind, reason: edge.reason, time: edge.time, mutual: false }; 90 + } else { 91 + entry[kind].mutual = true; 92 + } 93 + } else { 94 + if (!entry.follows) entry.follows = []; 95 + entry.follows.push({ source: edge.source, target: edge.target, kind: edge.kind, reason: edge.reason, time: edge.time, mutual: false }); 96 + } 97 + } 98 + 99 + for (const entry of pairIndex.values()) { 100 + if (entry.vouch && entry.denounce) { 101 + nodeSet.add(entry.vouch.source); nodeSet.add(entry.vouch.target); 102 + links.push({ source: entry.vouch.source, target: entry.vouch.target, kind: 'vouch/mixed', reason: entry.vouch.reason, time: entry.vouch.time, mutual: true }); 103 + } else if (entry.vouch) { 104 + nodeSet.add(entry.vouch.source); nodeSet.add(entry.vouch.target); 105 + links.push(entry.vouch); 106 + } else if (entry.denounce) { 107 + nodeSet.add(entry.denounce.source); nodeSet.add(entry.denounce.target); 108 + links.push(entry.denounce); 109 + } 110 + if (entry.follows) { 111 + for (const fl of entry.follows) { 112 + nodeSet.add(fl.source); nodeSet.add(fl.target); 113 + links.push(fl); 114 + } 115 + } 116 + } 117 + 118 + const nodes = [...nodeSet].map(id => { 119 + const p = profileMap[id] || {}; 120 + return { id, label: p.handle || id, handle: p.handle || '', avatar: p.avatar || '' }; 121 + }); 122 + 123 + // pre-compute degrees and vouch/denounce counts 124 + nodeDegrees = {}; 125 + nodeInVouch = {}; 126 + nodeInDenounce = {}; 127 + nodeOutVouch = {}; 128 + nodeOutDenounce = {}; 129 + for (const n of nodes) { nodeDegrees[n.id] = 0; nodeInVouch[n.id] = 0; nodeInDenounce[n.id] = 0; nodeOutVouch[n.id] = 0; nodeOutDenounce[n.id] = 0; } 130 + for (const link of links) { 131 + nodeDegrees[link.source] = (nodeDegrees[link.source] || 0) + 1; 132 + nodeDegrees[link.target] = (nodeDegrees[link.target] || 0) + 1; 133 + if (link.kind === 'vouch/vouch') { nodeInVouch[link.target]++; nodeOutVouch[link.source]++; } 134 + else if (link.kind === 'vouch/denounce') { nodeInDenounce[link.target]++; nodeOutDenounce[link.source]++; } 135 + else if (link.kind === 'vouch/mixed') { nodeInVouch[link.target]++; nodeInDenounce[link.target]++; nodeOutVouch[link.source]++; nodeOutDenounce[link.source]++; } 136 + } 137 + 138 + // pre-compute node colors 139 + const isDark = isDarkMode(); 140 + nodeColors = {}; 141 + for (const n of nodes) { 142 + const vouches = nodeInVouch[n.id] || 0; 143 + const denounces = nodeInDenounce[n.id] || 0; 144 + const total = vouches + denounces; 145 + if (total === 0) { nodeColors[n.id] = isDark ? '#6e738d' : '#9ca0b0'; continue; } 146 + const ratio = denounces / total; 147 + if (ratio > 0.5) nodeColors[n.id] = isDark ? '#ed8796' : '#d20f39'; 148 + else if (ratio > 0.1) nodeColors[n.id] = isDark ? '#eed49f' : '#df8e1d'; 149 + else nodeColors[n.id] = isDark ? '#a6da95' : '#40a02b'; 150 + } 151 + 152 + return { nodes, links }; 153 + } 154 + 155 + function edgeColor(kind) { 156 + switch (kind) { 157 + case 'vouch/vouch': return isDarkMode() ? 'rgba(166,218,149,0.6)' : 'rgba(64,160,43,0.6)'; 158 + case 'vouch/denounce': return isDarkMode() ? 'rgba(237,135,150,0.6)' : 'rgba(210,15,57,0.6)'; 159 + case 'vouch/mixed': return isDarkMode() ? 'rgba(238,212,159,0.6)' : 'rgba(223,142,29,0.6)'; 160 + case 'follow': return isDarkMode() ? 'rgba(198,160,246,0.55)' : 'rgba(136,57,239,0.55)'; 161 + default: return 'rgba(107,114,128,0.3)'; 162 + } 163 + } 164 + 165 + function edgeWidth(kind, mutual) { 166 + const base = (() => { 167 + switch (kind) { 168 + case 'vouch/vouch': return 0.8; 169 + case 'vouch/denounce': return 0.6; 170 + case 'vouch/mixed': return 0.8; 171 + case 'follow': return 0.4; 172 + default: return 0.4; 173 + } 174 + })(); 175 + return mutual ? base + 0.2 : base; 176 + } 177 + 178 + async function loadData() { 179 + const resp = await fetch('/api/graph'); 180 + graphData = await resp.json(); 181 + 182 + if (!graphData.edges || graphData.edges.length === 0) { 183 + loading.textContent = 'No data yet. Run backfill or ingest to collect records...'; 184 + return; 185 + } 186 + 187 + for (const node of graphData.nodes) { 188 + if (node.handle || node.avatar) { 189 + profileMap[node.id] = { handle: node.handle || '', avatar: node.avatar || '' }; 190 + } 191 + } 192 + 193 + loading.style.display = 'none'; 194 + initGraph(); 195 + } 196 + 197 + async function initGraph() { 198 + const { nodes, links } = buildGraph(); 199 + 200 + if (nodes.length === 0) return; 201 + 202 + currentNodes = nodes; 203 + currentLinks = links; 204 + 205 + // Build Cosmograph data format 206 + const rawPoints = nodes.map(n => ({ 207 + id: n.id, 208 + color: nodeColors[n.id] || '#9ca0b0', 209 + size: Math.max(5, Math.min(20, 4 * Math.log2((nodeDegrees[n.id] || 0) + 2))), 210 + label: n.handle || '', 211 + imageUrl: n.avatar ? `/api/proxy/avatar?url=${encodeURIComponent(n.avatar)}` : '', 212 + })); 213 + 214 + const rawLinks = links.map(l => ({ 215 + source: l.source, 216 + target: l.target, 217 + color: edgeColor(l.kind), 218 + width: edgeWidth(l.kind, l.mutual), 219 + arrow: l.kind.startsWith('vouch/'), 220 + strength: l.kind === 'follow' ? 0.3 : 0.8, 221 + })); 222 + 223 + const dataConfig = { 224 + points: { 225 + pointIdBy: 'id', 226 + pointColorBy: 'color', 227 + pointSizeBy: 'size', 228 + pointSizeStrategy: 'direct', 229 + pointLabelBy: 'label', 230 + pointImageUrlBy: 'imageUrl', 231 + pointImageSize: 20, 232 + hidePointShapesForLoadedImages: true, 233 + }, 234 + links: { 235 + linkSourceBy: 'source', 236 + linkTargetsBy: ['target'], 237 + linkColorBy: 'color', 238 + linkWidthBy: 'width', 239 + linkWidthStrategy: 'direct', 240 + linkArrowBy: 'arrow', 241 + linkStrengthBy: 'strength', 242 + }, 243 + }; 244 + 245 + const result = await prepareCosmographData(dataConfig, rawPoints, rawLinks); 246 + if (!result) return; 247 + 248 + const { points, links: prepLinks, cosmographConfig } = result; 249 + 250 + // Store for callback access after filter toggles 251 + currentRawPoints = rawPoints; 252 + 253 + // Build index map for lookup 254 + pointIdToIndex = {}; 255 + for (let i = 0; i < rawPoints.length; i++) { 256 + pointIdToIndex[rawPoints[i].id] = i; 257 + } 258 + 259 + const isDark = isDarkMode(); 260 + 261 + if (cosmograph) { 262 + await cosmograph.destroy(); 263 + cosmograph = null; 264 + } 265 + 266 + cosmograph = new Cosmograph(container, { 267 + points, 268 + links: prepLinks, 269 + ...cosmographConfig, 270 + backgroundColor: isDark ? '#111827' : '#f1f5f9', 271 + showHoveredPointLabel: true, 272 + hoveredPointLabelClassName: 'hovered-label', 273 + focusPointOnClick: true, 274 + selectPointOnClick: 'single', 275 + pointDefaultColor: isDark ? '#6e738d' : '#9ca0b0', 276 + pointSizeRange: [5, 20], 277 + linkDefaultColor: isDark ? 'rgba(107,114,128,0.2)' : 'rgba(107,114,128,0.2)', 278 + linkDefaultWidth: 0.5, 279 + linkWidthStrategy: 'direct', 280 + linkWidthRange: [0.4, 0.8], 281 + enableSimulation: true, 282 + onPointMouseOver: (pointIndex) => { 283 + const id = currentRawPoints[pointIndex]?.id; 284 + if (!id) return; 285 + showNodeTooltip(id, pointIndex); 286 + }, 287 + onPointMouseOut: () => { 288 + tooltip.style.display = 'none'; 289 + }, 290 + onPointClick: (pointIndex) => { 291 + const id = currentRawPoints[pointIndex]?.id; 292 + if (!id) return; 293 + const p = profileMap[id]; 294 + if (p?.handle && p.handle !== '!') { 295 + window.open(`https://tangled.org/@${p.handle}`, '_blank'); 296 + } 297 + }, 298 + onLinkMouseOver: (linkIndex) => { 299 + const link = currentLinks[linkIndex]; 300 + if (!link) return; 301 + showLinkTooltip(link); 302 + }, 303 + onLinkMouseOut: () => { 304 + tooltip.style.display = 'none'; 305 + }, 306 + onGraphRebuilt: (stats) => { 307 + updateStats(currentNodes, currentLinks); 308 + cosmograph?.fitView(800, 50); 309 + }, 310 + }); 311 + 312 + document.addEventListener('keydown', e => { 313 + if (e.code === 'Space' && e.target === document.body) { 314 + e.preventDefault(); 315 + cosmograph?.fitView(400, 30); 316 + } 317 + }); 318 + 319 + updateStats(currentNodes, currentLinks); 320 + } 321 + 322 + function showNodeTooltip(did, pointIndex) { 323 + const profile = profileMap[did] || {}; 324 + const handle = profile.handle || ''; 325 + const avatar = profile.avatar || ''; 326 + const avatarHtml = avatar 327 + ? `<img class="tooltip-avatar" src="/api/proxy/avatar?url=${encodeURIComponent(avatar)}" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"><div class="tooltip-avatar-fallback" style="display:none">${(handle || did).charAt(0).toUpperCase()}</div>` 328 + : `<div class="tooltip-avatar-fallback">${(handle || shortenDID(did)).charAt(0).toUpperCase()}</div>`; 329 + 330 + const inV = nodeInVouch[did] || 0; 331 + const inD = nodeInDenounce[did] || 0; 332 + const outV = nodeOutVouch[did] || 0; 333 + const outD = nodeOutDenounce[did] || 0; 334 + const total = inV + inD; 335 + let trustLine = ''; 336 + if (total > 0 || outV + outD > 0) { 337 + const pct = total > 0 ? Math.round((inV / total) * 100) : null; 338 + let pctStr; 339 + if (pct !== null) { 340 + const c = pct > 90 ? 'kv' : pct > 50 ? 'ks' : 'kd'; 341 + pctStr = `<span class="${c}">${pct}%</span>`; 342 + } else { 343 + pctStr = '<span style="color:var(--text-muted)">?</span>'; 344 + } 345 + const inPart = total > 0 ? `<span class="kv">${inV}v</span> <span class="kd">${inD}d</span> in` : ''; 346 + const outPart = outV + outD > 0 ? `<span class="kv">${outV}v</span> <span class="kd">${outD}d</span> out` : ''; 347 + const sep = inPart && outPart ? ' · ' : ''; 348 + trustLine = `<div style="margin-top:4px;font-size:0.6875rem">${pctStr} trusted · ${inPart}${sep}${outPart}</div>`; 349 + } 350 + 351 + tooltip.innerHTML = ` 352 + <div class="tooltip-profile"> 353 + ${avatarHtml} 354 + <div> 355 + <div class="tooltip-name">${handle || shortenDID(did)}</div> 356 + ${handle ? `<div class="tooltip-handle">${shortenDID(did)}</div>` : ''} 357 + ${trustLine} 358 + </div> 359 + </div> 360 + `; 361 + tooltip.style.display = 'block'; 362 + } 363 + 364 + function showLinkTooltip(link) { 365 + const srcId = link.source; 366 + const tgtId = link.target; 367 + const srcP = profileMap[srcId] || {}; 368 + const tgtP = profileMap[tgtId] || {}; 369 + const kindClass = link.kind === 'vouch/denounce' ? 'kd' 370 + : link.kind === 'vouch/vouch' ? 'kv' 371 + : link.kind === 'vouch/mixed' ? 'ks' : 'kf'; 372 + const label = link.kind === 'vouch/denounce' ? 'denounce' 373 + : link.kind === 'vouch/vouch' ? 'vouch' 374 + : link.kind === 'vouch/mixed' ? 'mixed' : 'follow'; 375 + const mutualTag = link.mutual ? ' (mutual)' : ''; 376 + 377 + const srcAvatar = srcP.avatar 378 + ? `<img class="tooltip-avatar" src="/api/proxy/avatar?url=${encodeURIComponent(srcP.avatar)}" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"><div class="tooltip-avatar-fallback" style="display:none">${(srcP.handle || srcId).charAt(0).toUpperCase()}</div>` 379 + : `<div class="tooltip-avatar-fallback">${(srcP.handle || shortenDID(srcId)).charAt(0).toUpperCase()}</div>`; 380 + const tgtAvatar = tgtP.avatar 381 + ? `<img class="tooltip-avatar" src="/api/proxy/avatar?url=${encodeURIComponent(tgtP.avatar)}" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"><div class="tooltip-avatar-fallback" style="display:none">${(tgtP.handle || tgtId).charAt(0).toUpperCase()}</div>` 382 + : `<div class="tooltip-avatar-fallback">${(tgtP.handle || shortenDID(tgtId)).charAt(0).toUpperCase()}</div>`; 383 + 384 + tooltip.innerHTML = ` 385 + <div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap"> 386 + ${srcAvatar} 387 + <span class="did">${srcP.handle || shortenDID(srcId)}</span> 388 + <span class="${kindClass} action-word">${label}s${mutualTag}</span> 389 + ${tgtAvatar} 390 + <span class="did">${tgtP.handle || shortenDID(tgtId)}</span> 391 + </div> 392 + ${link.reason ? `<div style="margin-top:4px;color:var(--text-muted);word-break:break-word">${link.reason}</div>` : ''} 393 + `; 394 + tooltip.style.display = 'block'; 395 + } 396 + 397 + function updateStats(nodes, links) { 398 + document.getElementById('stat-nodes').textContent = nodes.length; 399 + let vouches = 0, denounces = 0, mixed = 0, follows = 0; 400 + for (const l of links) { 401 + switch (l.kind) { 402 + case 'vouch/vouch': vouches++; break; 403 + case 'vouch/denounce': denounces++; break; 404 + case 'vouch/mixed': mixed++; break; 405 + case 'follow': follows++; break; 406 + } 407 + } 408 + document.getElementById('stat-vouches').textContent = vouches; 409 + document.getElementById('stat-denounces').textContent = denounces; 410 + document.getElementById('stat-mixed').textContent = mixed; 411 + document.getElementById('stat-follows').textContent = follows; 412 + headerStats.textContent = `${nodes.length} nodes / ${links.length} edges`; 413 + } 414 + 415 + async function rebuildGraph() { 416 + await initGraph(); 417 + } 418 + 419 + // --- search --- 420 + let searchTimeout = null; 421 + 422 + searchInput.addEventListener('input', () => { 423 + clearTimeout(searchTimeout); 424 + const q = searchInput.value.trim(); 425 + if (q.length < 2) { searchDropdown.style.display = 'none'; return; } 426 + searchTimeout = setTimeout(async () => { 427 + try { 428 + const resp = await fetch(`/api/search?q=${encodeURIComponent(q)}`); 429 + if (!resp.ok) return; 430 + const data = await resp.json(); 431 + const actors = data.actors || []; 432 + if (actors.length === 0) { searchDropdown.style.display = 'none'; return; } 433 + searchDropdown.innerHTML = actors.map(a => { 434 + const avatar = a.avatar 435 + ? `<img src="/api/proxy/avatar?url=${encodeURIComponent(a.avatar)}" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"><div class="search-fallback" style="display:none">${a.handle.charAt(0).toUpperCase()}</div>` 436 + : `<div class="search-fallback">${a.handle.charAt(0).toUpperCase()}</div>`; 437 + return `<div class="search-item" data-did="${a.did}"> 438 + ${avatar} 439 + <div class="search-info"> 440 + <div class="search-handle">${a.handle}</div> 441 + <div class="search-did">${shortenDID(a.did)}</div> 442 + </div> 443 + </div>`; 444 + }).join(''); 445 + searchDropdown.style.display = 'block'; 446 + } catch {} 447 + }, 250); 448 + }); 449 + 450 + searchDropdown.addEventListener('click', e => { 451 + const item = e.target.closest('.search-item'); 452 + if (!item) return; 453 + const did = item.dataset.did; 454 + searchInput.value = ''; 455 + searchDropdown.style.display = 'none'; 456 + if (cosmograph) { 457 + const idx = pointIdToIndex[did]; 458 + if (idx !== undefined) { 459 + cosmograph.zoomToPoint(idx, 800, 6); 460 + } 461 + } 462 + }); 463 + 464 + document.addEventListener('click', e => { 465 + if (!document.getElementById('search-wrap').contains(e.target)) { 466 + searchDropdown.style.display = 'none'; 467 + } 468 + }); 469 + 470 + searchInput.addEventListener('keydown', e => { 471 + if (e.key === 'Escape') { 472 + searchDropdown.style.display = 'none'; 473 + searchInput.blur(); 474 + } 475 + }); 476 + 477 + // Tooltip follows mouse 478 + document.addEventListener('mousemove', e => { 479 + if (tooltip.style.display === 'block') { 480 + tooltip.style.left = (e.clientX + 14) + 'px'; 481 + tooltip.style.top = (e.clientY + 14) + 'px'; 482 + } 483 + }); 484 + 485 + loadData();
+284
web/style.css
··· 1 + :root { 2 + --bg-page: #f1f5f9; 3 + --bg-card: #ffffff; 4 + --bg-input: #f9fafb; 5 + --text-primary: #000000; 6 + --text-secondary: #4b5563; 7 + --text-muted: #6b7280; 8 + --border-default: #e5e7eb; 9 + --border-strong: #d1d5db; 10 + --vouch: #40a02b; 11 + --vouch-edge: rgba(64,160,43,0.6); 12 + --denounce: #d20f39; 13 + --denounce-edge: rgba(210,15,57,0.6); 14 + --follow: #8839ef; 15 + --follow-edge: rgba(136,57,239,0.55); 16 + } 17 + 18 + @media (prefers-color-scheme: dark) { 19 + :root { 20 + --bg-page: #111827; 21 + --bg-card: #1f2937; 22 + --bg-input: #1f2937; 23 + --text-primary: #ffffff; 24 + --text-secondary: #d1d5db; 25 + --text-muted: #9ca3af; 26 + --border-default: #374151; 27 + --border-strong: #4b5563; 28 + --vouch: #a6da95; 29 + --vouch-edge: rgba(166,218,149,0.6); 30 + --denounce: #ed8796; 31 + --denounce-edge: rgba(237,135,150,0.6); 32 + --follow: #c6a0f6; 33 + --follow-edge: rgba(198,160,246,0.55); 34 + } 35 + } 36 + 37 + * { margin: 0; padding: 0; box-sizing: border-box; } 38 + html, body { height: 100%; overflow: hidden; } 39 + html { font-size: 14px; } 40 + 41 + body { 42 + font-family: 'Inter', system-ui, -apple-system, sans-serif; 43 + background: var(--bg-page); 44 + color: var(--text-primary); 45 + display: flex; 46 + flex-direction: column; 47 + transition: background-color 0.2s, color 0.2s; 48 + } 49 + 50 + header { 51 + flex-shrink: 0; 52 + background: var(--bg-card); 53 + border-bottom: 1px solid var(--border-default); 54 + padding: 0.5rem 1.5rem; 55 + display: flex; 56 + justify-content: space-between; 57 + align-items: center; 58 + z-index: 20; 59 + } 60 + 61 + .logo { 62 + display: flex; 63 + align-items: center; 64 + gap: 0.5rem; 65 + text-decoration: none; 66 + color: var(--text-primary); 67 + } 68 + .logo svg { width: 28px; height: 28px; } 69 + .logo-text { font-weight: 700; font-size: 1.25rem; } 70 + .logo-badge { 71 + font-weight: 500; 72 + font-size: 0.6875rem; 73 + padding: 0.125rem 0.375rem; 74 + border-radius: 0.25rem; 75 + background: var(--bg-input); 76 + border: 1px solid var(--border-default); 77 + } 78 + 79 + header nav { 80 + display: flex; 81 + align-items: center; 82 + gap: 1rem; 83 + font-size: 0.875rem; 84 + font-family: 'IBM Plex Mono', ui-monospace, monospace; 85 + color: var(--text-muted); 86 + } 87 + 88 + #search-wrap { 89 + position: relative; 90 + } 91 + #search-input { 92 + font-family: 'IBM Plex Mono', ui-monospace, monospace; 93 + font-size: 0.8125rem; 94 + padding: 0.25rem 0.5rem; 95 + border: 1px solid var(--border-default); 96 + border-radius: 0.25rem; 97 + background: var(--bg-input); 98 + color: var(--text-primary); 99 + outline: none; 100 + width: 200px; 101 + box-shadow: inset 0 -2px 0 0 rgba(0,0,0,0.05); 102 + transition: border-color 0.15s, box-shadow 0.15s; 103 + } 104 + #search-input:focus { 105 + border-color: var(--border-strong); 106 + box-shadow: inset 0 -2px 0 0 rgba(0,0,0,0.1), 0 0 0 2px rgba(136,57,239,0.15); 107 + } 108 + #search-dropdown { 109 + position: absolute; 110 + top: 100%; 111 + left: 0; 112 + margin-top: 4px; 113 + background: var(--bg-card); 114 + border: 1px solid var(--border-default); 115 + border-radius: 0.25rem; 116 + box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); 117 + z-index: 30; 118 + display: none; 119 + min-width: 240px; 120 + max-height: 260px; 121 + overflow-y: auto; 122 + } 123 + .search-item { 124 + display: flex; 125 + align-items: center; 126 + gap: 0.5rem; 127 + padding: 0.375rem 0.5rem; 128 + cursor: pointer; 129 + transition: background 0.1s; 130 + } 131 + .search-item:hover { background: var(--bg-input); } 132 + .search-item img { 133 + width: 24px; height: 24px; border-radius: 50%; 134 + object-fit: cover; flex-shrink: 0; 135 + border: 1px solid var(--border-default); 136 + } 137 + .search-item .search-fallback { 138 + width: 24px; height: 24px; border-radius: 50%; 139 + background: var(--bg-input); border: 1px solid var(--border-default); 140 + display: flex; align-items: center; justify-content: center; 141 + font-size: 0.625rem; color: var(--text-muted); flex-shrink: 0; 142 + } 143 + .search-item .search-handle { 144 + font-size: 0.8125rem; color: var(--text-primary); font-weight: 500; 145 + } 146 + .search-item .search-did { 147 + font-size: 0.6875rem; color: var(--text-muted); 148 + } 149 + .search-item .search-info { overflow: hidden; } 150 + 151 + #graph-container { flex: 1; min-height: 0; overflow: hidden; } 152 + 153 + #controls { 154 + flex-shrink: 0; 155 + background: var(--bg-card); 156 + border-top: 1px solid var(--border-default); 157 + padding: 0.5rem 1.5rem; 158 + z-index: 20; 159 + } 160 + 161 + #controls-row { 162 + display: flex; 163 + align-items: center; 164 + gap: 0.5rem; 165 + flex-wrap: wrap; 166 + } 167 + 168 + .filter-btn { 169 + position: relative; 170 + z-index: 1; 171 + display: inline-flex; 172 + min-height: 30px; 173 + align-items: center; 174 + justify-content: center; 175 + padding: 0 0.5rem 0.2rem; 176 + font-size: 0.875rem; 177 + font-family: inherit; 178 + color: var(--text-muted); 179 + cursor: pointer; 180 + background: transparent; 181 + border: none; 182 + } 183 + .filter-btn::before { 184 + content: ''; 185 + position: absolute; 186 + inset: 0; 187 + z-index: -1; 188 + display: block; 189 + border-radius: 0.25rem; 190 + border: 1px solid var(--border-default); 191 + background: var(--bg-card); 192 + box-shadow: inset 0 -2px 0 0 rgba(0,0,0,0.1), 0 1px 0 0 rgba(0,0,0,0.04); 193 + transition: all 0.15s ease-in-out; 194 + } 195 + .filter-btn:hover::before { 196 + box-shadow: inset 0 -2px 0 0 rgba(0,0,0,0.15), 0 2px 1px 0 rgba(0,0,0,0.06); 197 + background: var(--bg-input); 198 + } 199 + .filter-btn:active::before { box-shadow: inset 0 2px 2px 0 rgba(0,0,0,0.1); } 200 + .filter-btn.active { color: var(--text-primary); } 201 + .filter-btn.active .filter-dot { box-shadow: 0 0 0 2px var(--bg-card), 0 0 0 3px currentColor; } 202 + .filter-btn:not(.active) .filter-dot { opacity: 0.4; } 203 + .filter-btn:not(.active)::before { box-shadow: none; background: var(--bg-input); } 204 + 205 + .filter-dot { 206 + display: inline-block; 207 + width: 8px; height: 8px; 208 + border-radius: 50%; 209 + margin-right: 6px; 210 + transition: opacity 0.15s, box-shadow 0.15s; 211 + } 212 + 213 + .spacer { flex: 1; } 214 + 215 + #stats-row { 216 + display: flex; 217 + gap: 0; 218 + font-size: 0.75rem; 219 + font-family: 'IBM Plex Mono', ui-monospace, monospace; 220 + color: var(--text-muted); 221 + align-items: center; 222 + } 223 + #stats-row span { color: var(--text-secondary); margin-left: 4px; margin-right: 3px; } 224 + 225 + #loading { 226 + position: fixed; 227 + top: 50%; left: 50%; 228 + transform: translate(-50%, -50%); 229 + font-size: 0.875rem; 230 + color: var(--text-muted); 231 + z-index: 20; 232 + } 233 + 234 + #tooltip { 235 + position: fixed; 236 + background: var(--bg-card); 237 + border: 1px solid var(--border-default); 238 + padding: 0.5rem 0.75rem; 239 + border-radius: 0.25rem; 240 + font-size: 0.75rem; 241 + font-family: 'IBM Plex Mono', ui-monospace, monospace; 242 + pointer-events: none; 243 + z-index: 15; 244 + display: none; 245 + max-width: 320px; 246 + box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); 247 + } 248 + #tooltip .did { color: var(--text-primary); word-break: break-all; } 249 + .kv { color: var(--vouch); } 250 + .kd { color: var(--denounce); } 251 + .kf { color: var(--follow); } 252 + .ks { color: #df8e1d; } 253 + .action-line { display: flex; align-items: baseline; gap: 0; flex-wrap: wrap; } 254 + .action-line .action-word { font-weight: 600; } 255 + #tooltip .tooltip-profile { 256 + display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.375rem; 257 + } 258 + #tooltip .tooltip-avatar { 259 + width: 32px; height: 32px; border-radius: 50%; 260 + border: 2px solid var(--border-default); 261 + object-fit: cover; flex-shrink: 0; 262 + } 263 + #tooltip .tooltip-avatar-fallback { 264 + width: 32px; height: 32px; border-radius: 50%; 265 + background: var(--bg-input); border: 2px solid var(--border-default); 266 + display: flex; align-items: center; justify-content: center; 267 + font-size: 0.75rem; color: var(--text-muted); flex-shrink: 0; 268 + } 269 + #tooltip .tooltip-name { font-weight: 600; color: var(--text-primary); font-size: 0.8125rem; } 270 + #tooltip .tooltip-handle { color: var(--text-muted); font-size: 0.6875rem; } 271 + 272 + ::selection { background: rgba(250,204,21,0.3); color: #000; } 273 + @media (prefers-color-scheme: dark) { 274 + ::selection { background: rgba(161,98,7,0.5); color: #fff; } 275 + } 276 + 277 + .hovered-label { 278 + color: white; 279 + background: #333841; 280 + padding: 2px 6px; 281 + border-radius: 3px; 282 + font-family: 'IBM Plex Mono', monospace; 283 + font-size: 11px; 284 + }