From 02656995041da2e695e35911fab0fa2972f55211 Mon Sep 17 00:00:00 2001 From: Keysat Date: Sun, 31 May 2026 21:04:48 -0500 Subject: [PATCH] Initial commit: Premier Gunner tracker + StartOS 0.4.0 s9pk package --- .gitignore | 6 + README.md | 85 + package-lock.json | 1490 +++++++++++++++++ package.json | 20 + public/css/styles.css | 195 +++ public/icons/favicon.svg | 24 + public/icons/icon-192.png | Bin 0 -> 22028 bytes public/icons/icon-512.png | Bin 0 -> 71481 bytes public/icons/icon-maskable-512.png | Bin 0 -> 66114 bytes public/icons/logo.svg | 24 + public/icons/maskable.svg | 23 + public/index.html | 34 + public/js/api.js | 46 + public/js/app.js | 366 ++++ public/js/dashboard.js | 172 ++ public/login.html | 41 + public/manifest.webmanifest | 16 + public/sw.js | 41 + public/vendor/chart.umd.min.js | 20 + s9pk/.dockerignore | 11 + s9pk/.gitignore | 9 + s9pk/AGENTS.md | 5 + s9pk/CLAUDE.md | 1 + s9pk/CONTRIBUTING.md | 43 + s9pk/Dockerfile | 17 + s9pk/LICENSE | 21 + s9pk/Makefile | 39 + s9pk/README.md | 141 ++ s9pk/TODO.md | 1 + s9pk/UPDATING.md | 17 + s9pk/assets/.gitkeep | 0 s9pk/icon.svg | 24 + s9pk/instructions.md | 25 + s9pk/package-lock.json | 359 ++++ s9pk/package.json | 23 + s9pk/s9pk.mk | 138 ++ s9pk/startos/actions/index.ts | 44 + s9pk/startos/backups.ts | 5 + s9pk/startos/dependencies.ts | 5 + s9pk/startos/fileModels/README.md | 5 + s9pk/startos/fileModels/store.ts | 13 + s9pk/startos/i18n/dictionaries/default.ts | 26 + .../startos/i18n/dictionaries/translations.ts | 52 + s9pk/startos/i18n/index.ts | 8 + s9pk/startos/index.ts | 11 + s9pk/startos/init/index.ts | 16 + s9pk/startos/interfaces.ts | 25 + s9pk/startos/main.ts | 47 + s9pk/startos/manifest/i18n.ts | 20 + s9pk/startos/manifest/index.ts | 29 + s9pk/startos/sdk.ts | 9 + s9pk/startos/utils.ts | 5 + s9pk/startos/versions/current.ts | 28 + s9pk/startos/versions/index.ts | 7 + s9pk/tsconfig.json | 11 + src/auth.js | 88 + src/config.js | 25 + src/db.js | 26 + src/routes/auth.js | 45 + src/routes/categories.js | 77 + src/routes/entries.js | 82 + src/routes/goals.js | 48 + src/routes/plans.js | 34 + src/routes/stats.js | 93 + src/schema.sql | 84 + src/seed.js | 74 + src/server.js | 59 + 67 files changed, 4578 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/css/styles.css create mode 100644 public/icons/favicon.svg create mode 100644 public/icons/icon-192.png create mode 100644 public/icons/icon-512.png create mode 100644 public/icons/icon-maskable-512.png create mode 100644 public/icons/logo.svg create mode 100644 public/icons/maskable.svg create mode 100644 public/index.html create mode 100644 public/js/api.js create mode 100644 public/js/app.js create mode 100644 public/js/dashboard.js create mode 100644 public/login.html create mode 100644 public/manifest.webmanifest create mode 100644 public/sw.js create mode 100644 public/vendor/chart.umd.min.js create mode 100644 s9pk/.dockerignore create mode 100644 s9pk/.gitignore create mode 100644 s9pk/AGENTS.md create mode 100644 s9pk/CLAUDE.md create mode 100644 s9pk/CONTRIBUTING.md create mode 100644 s9pk/Dockerfile create mode 100644 s9pk/LICENSE create mode 100644 s9pk/Makefile create mode 100644 s9pk/README.md create mode 100644 s9pk/TODO.md create mode 100644 s9pk/UPDATING.md create mode 100644 s9pk/assets/.gitkeep create mode 100644 s9pk/icon.svg create mode 100644 s9pk/instructions.md create mode 100644 s9pk/package-lock.json create mode 100644 s9pk/package.json create mode 100644 s9pk/s9pk.mk create mode 100644 s9pk/startos/actions/index.ts create mode 100644 s9pk/startos/backups.ts create mode 100644 s9pk/startos/dependencies.ts create mode 100644 s9pk/startos/fileModels/README.md create mode 100644 s9pk/startos/fileModels/store.ts create mode 100644 s9pk/startos/i18n/dictionaries/default.ts create mode 100644 s9pk/startos/i18n/dictionaries/translations.ts create mode 100644 s9pk/startos/i18n/index.ts create mode 100644 s9pk/startos/index.ts create mode 100644 s9pk/startos/init/index.ts create mode 100644 s9pk/startos/interfaces.ts create mode 100644 s9pk/startos/main.ts create mode 100644 s9pk/startos/manifest/i18n.ts create mode 100644 s9pk/startos/manifest/index.ts create mode 100644 s9pk/startos/sdk.ts create mode 100644 s9pk/startos/utils.ts create mode 100644 s9pk/startos/versions/current.ts create mode 100644 s9pk/startos/versions/index.ts create mode 100644 s9pk/tsconfig.json create mode 100644 src/auth.js create mode 100644 src/config.js create mode 100644 src/db.js create mode 100644 src/routes/auth.js create mode 100644 src/routes/categories.js create mode 100644 src/routes/entries.js create mode 100644 src/routes/goals.js create mode 100644 src/routes/plans.js create mode 100644 src/routes/stats.js create mode 100644 src/schema.sql create mode 100644 src/seed.js create mode 100644 src/server.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a85e13 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +data/ +*.log +.DS_Store +.env +.claude/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..71f3eb6 --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# ⚽ Premier Gunner + +A kid-friendly, mobile-friendly soccer training tracker. Gunner logs what he trains each +day, plans his week, chases goals, and watches the **Road to London** thermometer fill up +toward the reward: a trip to see Arsenal play in person. Installable as a PWA on his phone. + +## Features + +- **Single-password login** — everything lives behind one password. +- **Daily logging** — tap a category pill, bump the metric steppers (juggles, minutes, + shots, …), add optional notes. Almost no typing. +- **Categories Gunner controls** — Juggling, Left Foot, Shooting, Soccer Tennis, Dribbling + Drills, Soccer Golf, Backyard with Dad, 1-on-1 with Elijah, EPA Agility & Speed — and he + can add/edit/archive his own in Settings. +- **Weekly planning** — set objectives for upcoming days; stick to them or change his mind. +- **Dashboard** — total sessions, training days, streaks, a GitHub-style calendar heatmap, + a radar chart of training spread, line charts of improvement over time, goal progress + bars, and the main-goal thermometer. +- **Goals** — overall or per-category, by number of sessions, personal best, or running + total. Flag one as the ⭐ main goal to drive the thermometer. +- **PWA** — installable, offline app shell, custom Arsenal-red cannon icon. + +## Tech + +Node.js + Fastify + better-sqlite3 on the backend; a no-build vanilla-JS PWA frontend with +Chart.js (vendored locally). All data in a single SQLite file. No external services. + +## Run locally + +```bash +npm install +PG_PASSWORD=your-password npm start +# open http://localhost:3000 (default password is "gunner" if PG_PASSWORD is unset) +``` + +To start fresh, stop the server and delete the `data/` folder — categories and the starter +goals re-seed on next boot. + +## Configuration (environment variables) + +| Var | Default | Purpose | +| ------------------ | ------------------ | -------------------------------------------------- | +| `PG_PORT` | `3000` | HTTP port | +| `PG_HOST` | `0.0.0.0` | Bind address | +| `PG_DATA_DIR` | `./data` | Where the SQLite DB + WAL live (persist this!) | +| `PG_PASSWORD` | `gunner` (dev) | Plaintext password, hashed on first boot | +| `PG_PASSWORD_HASH` | — | Pre-computed bcrypt hash (preferred for prod) | +| `PG_COOKIE_SECRET` | auto-generated | Session cookie signing secret (persisted if unset) | +| `PG_SESSION_DAYS` | `30` | How long a login stays valid | + +The password can also be changed in-app under ⚙️ Settings. + +## Phase 2 — not yet built + +These were intentionally deferred (see the plan in chat): + +1. **StartOS 0.4.0 service package** — wrap this in an s9pk so it installs on Start9, with + `PG_DATA_DIR` mounted on a persistent volume and the password exposed as a service config + field. Serve it on the clearnet domain via Start9 pages + StartTunnel. + Docs: https://docs.start9.com/packaging/0.4.0.x/ · https://docs.start9.com/start-tunnel/1.0.x/ +2. **DGX Spark (Qwen3.6) AI coach** — a server-side proxy to the OpenAI-compatible endpoint + that, on login, reviews Gunner's objectives + full history and suggests what to train and + how to plan ahead; plus on-demand drill ideas per category. Kept server-side so the + endpoint and any key never reach the browser. + +## Project layout + +``` +src/ + server.js Fastify app, auth gate, route wiring + config.js env-driven config + data dir + db.js SQLite connection + schema bootstrap + schema.sql tables + seed.js default categories + starter goals + auth.js password hashing, sessions + routes/ auth, categories, entries, plans, goals, stats +public/ + index.html app shell (tabs: Today / Plan / Stats / Goals) + login.html + css/styles.css + js/app.js views: logging, planning, goals, settings + js/dashboard.js charts, heatmap, thermometer + js/api.js fetch wrapper + manifest.webmanifest, sw.js, icons/ +``` diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..88dbdcf --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1490 @@ +{ + "name": "premier-gunner", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "premier-gunner", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@fastify/cookie": "^11.0.2", + "@fastify/static": "^8.1.1", + "bcryptjs": "^3.0.2", + "better-sqlite3": "^11.10.0", + "fastify": "^5.2.1" + } + }, + "node_modules/@fastify/accept-negotiator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.0.1.tgz", + "integrity": "sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.5.tgz", + "integrity": "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0" + } + }, + "node_modules/@fastify/cookie": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@fastify/cookie/-/cookie-11.0.2.tgz", + "integrity": "sha512-GWdwdGlgJxyvNv+QcKiGNevSspMQXncjMZ1J8IvuDQk0jvkzgWWZFNC2En3s+nHndZBGV8IbLwOI/sxCZw/mzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "cookie": "^1.0.0", + "fastify-plugin": "^5.0.0" + } + }, + "node_modules/@fastify/error": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", + "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz", + "integrity": "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^6.0.0" + } + }, + "node_modules/@fastify/forwarded": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.1.tgz", + "integrity": "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", + "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@fastify/proxy-addr": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz", + "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" + } + }, + "node_modules/@fastify/send": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@fastify/send/-/send-4.1.0.tgz", + "integrity": "sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@lukeed/ms": "^2.0.2", + "escape-html": "~1.0.3", + "fast-decode-uri-component": "^1.0.1", + "http-errors": "^2.0.0", + "mime": "^3" + } + }, + "node_modules/@fastify/static": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-8.3.0.tgz", + "integrity": "sha512-yKxviR5PH1OKNnisIzZKmgZSus0r2OZb8qCSbqmw34aolT4g3UlzYfeBRym+HJ1J471CR8e2ldNub4PubD1coA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/accept-negotiator": "^2.0.0", + "@fastify/send": "^4.0.0", + "content-disposition": "^0.5.4", + "fastify-plugin": "^5.0.0", + "fastq": "^1.17.1", + "glob": "^11.0.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "license": "MIT" + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.2.0.tgz", + "integrity": "sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, + "node_modules/better-sqlite3": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz", + "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stringify": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.4.0.tgz", + "integrity": "sha512-ibRCQ0GZKJIQ+P3Et1h0LhPgp3PMTYk0MH8O+kW3lNYsvmaQww5Nn3f1jf73Q0jR1Yz3a1CDP4/NZD3vOajWJQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0", + "json-schema-ref-resolver": "^3.0.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastify": { + "version": "5.8.5", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.8.5.tgz", + "integrity": "sha512-Yqptv59pQzPgQUSIm87hMqHJmdkb1+GPxdE6vW6FRyVE9G86mt7rOghitiU4JHRaTyDUk9pfeKmDeu70lAwM4Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^4.0.5", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^6.0.0", + "find-my-way": "^9.0.0", + "light-my-request": "^6.0.0", + "pino": "^9.14.0 || ^10.1.0", + "process-warning": "^5.0.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^4.0.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/fastify-plugin": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.1.0.tgz", + "integrity": "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/find-my-way": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.6.0.tgz", + "integrity": "sha512-Zf4Xve4RymLl7NgaavNebZ01joJ8MfVerOG43wy7SHLO+r+K0C6d/SE0BiR7AV5V1VOCFlOP7ecdo+I4qmiHrQ==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz", + "integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/json-schema-ref-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", + "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/light-my-request": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", + "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^1.0.1", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" + } + }, + "node_modules/light-my-request/node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "3.92.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", + "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pino": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex2": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.1.tgz", + "integrity": "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + }, + "bin": { + "safe-regex2": "bin/safe-regex2.js" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/thread-stream": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz", + "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==", + "license": "MIT", + "dependencies": { + "real-require": "^1.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/thread-stream/node_modules/real-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz", + "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==", + "license": "MIT" + }, + "node_modules/toad-cache": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.1.tgz", + "integrity": "sha512-5DXWzE4Vz7xNHsv+xQ+MGfJYyC78Aok3tEr0MNwHoRf7vZnga1mQXZ4/Nsodld4VR6Wd+VhfmqnNrsRJyYPfrQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6bec2aa --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "premier-gunner", + "version": "0.1.0", + "description": "Premier Gunner — kid-friendly soccer training tracker (PWA)", + "type": "module", + "main": "src/server.js", + "scripts": { + "start": "node src/server.js", + "dev": "node --watch src/server.js", + "seed": "node src/seed.js" + }, + "license": "MIT", + "dependencies": { + "@fastify/cookie": "^11.0.2", + "@fastify/static": "^8.1.1", + "bcryptjs": "^3.0.2", + "better-sqlite3": "^11.10.0", + "fastify": "^5.2.1" + } +} diff --git a/public/css/styles.css b/public/css/styles.css new file mode 100644 index 0000000..0479d5c --- /dev/null +++ b/public/css/styles.css @@ -0,0 +1,195 @@ +:root { + --arsenal-red: #EF0107; + --arsenal-red-dark: #c50006; + --arsenal-navy: #023474; + --gold: #DB0007; + --ink: #15181f; + --muted: #6b7280; + --bg: #f4f6fb; + --card: #ffffff; + --line: #e6e9f0; + --good: #198754; + --radius: 18px; + --shadow: 0 4px 18px rgba(20, 24, 31, 0.08); + --tap: 52px; + --safe-bottom: env(safe-area-inset-bottom, 0px); +} + +* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; } +html, body { margin: 0; padding: 0; } +body { + font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; + background: var(--bg); + color: var(--ink); + font-size: 17px; + line-height: 1.4; +} +h1, h2, h3 { margin: 0 0 .4em; } +button { font-family: inherit; cursor: pointer; } + +/* ---------- Login ---------- */ +.login-body { + min-height: 100vh; + display: grid; + place-items: center; + background: linear-gradient(160deg, var(--arsenal-red) 0%, var(--arsenal-red-dark) 55%, var(--arsenal-navy) 100%); + padding: 24px; +} +.login-card { + background: var(--card); + border-radius: 28px; + padding: 34px 28px; + width: 100%; + max-width: 360px; + text-align: center; + box-shadow: 0 20px 60px rgba(0, 0, 0, .3); +} +.login-logo { margin-bottom: 8px; } +.login-card h1 { color: var(--arsenal-red); font-size: 1.7rem; } +.tagline { color: var(--muted); margin-top: -4px; } +.login-card form { display: flex; flex-direction: column; gap: 14px; margin-top: 18px; } +input, select, textarea { + font-family: inherit; font-size: 1rem; + padding: 14px 16px; border: 2px solid var(--line); border-radius: 14px; + width: 100%; background: #fff; color: var(--ink); +} +input:focus, select:focus, textarea:focus { outline: none; border-color: var(--arsenal-red); } +.error { color: var(--arsenal-red); font-weight: 600; } + +/* ---------- App shell ---------- */ +.app-header { + position: sticky; top: 0; z-index: 20; + display: flex; align-items: center; gap: 10px; + padding: 12px 16px; padding-top: calc(12px + env(safe-area-inset-top, 0px)); + background: var(--arsenal-red); color: #fff; + box-shadow: var(--shadow); +} +.app-header h1 { font-size: 1.2rem; margin: 0; flex: 1; } +.icon-btn { + background: rgba(255,255,255,.18); border: none; color: #fff; + width: 40px; height: 40px; border-radius: 12px; font-size: 1.2rem; +} +.view { padding: 16px 16px 96px; max-width: 760px; margin: 0 auto; } + +/* ---------- Tab bar ---------- */ +.tabbar { + position: fixed; bottom: 0; left: 0; right: 0; z-index: 20; + display: grid; grid-template-columns: repeat(4, 1fr); + background: var(--card); border-top: 1px solid var(--line); + padding-bottom: var(--safe-bottom); +} +.tab { + background: none; border: none; padding: 10px 4px 8px; + font-size: 1.5rem; color: var(--muted); + display: flex; flex-direction: column; align-items: center; gap: 2px; +} +.tab span { font-size: .7rem; font-weight: 600; } +.tab.active { color: var(--arsenal-red); } + +/* ---------- Cards & layout ---------- */ +.card { + background: var(--card); border-radius: var(--radius); + padding: 16px; margin-bottom: 16px; box-shadow: var(--shadow); +} +.card h2 { font-size: 1.05rem; } +.row { display: flex; align-items: center; gap: 10px; } +.spread { justify-content: space-between; } +.muted { color: var(--muted); } +.small { font-size: .85rem; } +.center { text-align: center; } +.hidden, [hidden] { display: none !important; } + +/* ---------- Date nav ---------- */ +.datenav { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 14px; } +.datenav .day-label { font-weight: 700; font-size: 1.05rem; text-align: center; flex: 1; } +.datenav button { width: 44px; height: 44px; border-radius: 12px; border: none; background: #fff; box-shadow: var(--shadow); font-size: 1.2rem; } + +/* ---------- Pills ---------- */ +.pill-grid { display: flex; flex-wrap: wrap; gap: 10px; } +.pill { + border: 2px solid var(--line); background: #fff; border-radius: 999px; + padding: 10px 16px; font-size: 1rem; font-weight: 600; + display: inline-flex; align-items: center; gap: 8px; min-height: var(--tap); + transition: transform .05s ease; +} +.pill:active { transform: scale(.96); } +.pill .emoji { font-size: 1.3rem; } +.pill.selected { color: #fff; border-color: transparent; } +.pill.done { box-shadow: inset 0 0 0 2px var(--good); } +.pill .check { color: #fff; } + +/* ---------- Buttons ---------- */ +.btn-primary { + background: var(--arsenal-red); color: #fff; border: none; + border-radius: 14px; padding: 14px 18px; font-size: 1rem; font-weight: 700; + min-height: var(--tap); +} +.btn-primary.big { font-size: 1.1rem; padding: 16px; } +.btn-ghost { background: #fff; color: var(--ink); border: 2px solid var(--line); border-radius: 14px; padding: 12px 16px; font-weight: 600; } +.btn-danger { background: none; border: none; color: var(--arsenal-red); font-weight: 700; } +.btn-row { display: flex; gap: 10px; margin-top: 8px; } +.btn-row > * { flex: 1; } + +/* ---------- Steppers ---------- */ +.metric { margin: 14px 0; } +.metric label { display: block; font-weight: 600; margin-bottom: 6px; } +.stepper { display: flex; align-items: center; gap: 12px; } +.stepper button { width: 48px; height: 48px; border-radius: 50%; border: none; background: var(--arsenal-red); color: #fff; font-size: 1.6rem; font-weight: 700; } +.stepper .val { font-size: 1.6rem; font-weight: 800; min-width: 70px; text-align: center; } +.stepper .unit { color: var(--muted); font-size: .9rem; } + +/* ---------- Logged entries ---------- */ +.entry { display: flex; align-items: center; gap: 10px; padding: 10px 0; border-bottom: 1px solid var(--line); } +.entry:last-child { border-bottom: none; } +.entry .emoji { font-size: 1.4rem; } +.entry .vals { color: var(--muted); font-size: .9rem; } +.badge { display: inline-block; background: var(--bg); border-radius: 999px; padding: 2px 10px; font-size: .8rem; font-weight: 600; } + +/* ---------- Stats ---------- */ +.stat-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-bottom: 16px; } +.stat { background: var(--card); border-radius: var(--radius); padding: 16px; text-align: center; box-shadow: var(--shadow); } +.stat .num { font-size: 2rem; font-weight: 800; color: var(--arsenal-red); } +.stat .lbl { color: var(--muted); font-size: .8rem; font-weight: 600; } + +/* Thermometer */ +.thermo-wrap { display: flex; align-items: center; gap: 18px; } +.thermo { position: relative; width: 46px; height: 200px; } +.thermo .reward { flex: 1; } +.thermo .reward .pct { font-size: 1.8rem; font-weight: 800; color: var(--arsenal-red); } + +/* Heatmap */ +.heatmap { overflow-x: auto; padding-bottom: 6px; } +.heat-cell { rx: 2; } + +/* Progress bars */ +.goal { margin-bottom: 16px; } +.goal .top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; } +.goal .label { font-weight: 700; } +.bar { height: 14px; background: var(--bg); border-radius: 999px; overflow: hidden; margin-top: 6px; } +.bar > span { display: block; height: 100%; background: linear-gradient(90deg, var(--arsenal-red), #ff5a5f); border-radius: 999px; transition: width .5s ease; } +.bar.done > span { background: linear-gradient(90deg, var(--good), #34d399); } + +canvas { max-width: 100%; } + +/* ---------- Modal ---------- */ +.modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.45); display: grid; place-items: end center; z-index: 40; } +.modal { + background: var(--card); width: 100%; max-width: 760px; + border-radius: 24px 24px 0 0; padding: 20px 18px calc(20px + var(--safe-bottom)); + max-height: 92vh; overflow-y: auto; box-shadow: 0 -10px 40px rgba(0,0,0,.25); +} +.modal h2 { display: flex; justify-content: space-between; align-items: center; } +.modal .close { background: none; border: none; font-size: 1.6rem; color: var(--muted); } +.field { margin-bottom: 14px; } +.field label { display: block; font-weight: 600; margin-bottom: 6px; } +.emoji-pick { display: flex; flex-wrap: wrap; gap: 6px; } +.emoji-pick button { font-size: 1.4rem; width: 44px; height: 44px; border-radius: 12px; border: 2px solid var(--line); background: #fff; } +.emoji-pick button.sel { border-color: var(--arsenal-red); background: #fff0f0; } + +.empty { text-align: center; color: var(--muted); padding: 30px 10px; } +.empty .big-emoji { font-size: 3rem; display: block; margin-bottom: 8px; } +.toast { position: fixed; left: 50%; bottom: 84px; transform: translateX(-50%); background: var(--ink); color: #fff; padding: 12px 20px; border-radius: 999px; font-weight: 600; z-index: 60; box-shadow: var(--shadow); } + +@media (min-width: 620px) { + .stat-grid { grid-template-columns: repeat(4, 1fr); } +} diff --git a/public/icons/favicon.svg b/public/icons/favicon.svg new file mode 100644 index 0000000..51785c1 --- /dev/null +++ b/public/icons/favicon.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/icon-192.png b/public/icons/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..a98eefd3604fa1b888e87d124b8a7e882ea6fc16 GIT binary patch literal 22028 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�F+P^!cNG$2z&$N1mpm3+qLQdQGv6Bn)xjQ$vgkn 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

QG=oW|5k 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 UkVN|s8~8hS#>S$;?ApWs192JL$N&HU literal 0 HcmV?d00001 diff --git a/public/icons/logo.svg b/public/icons/logo.svg new file mode 100644 index 0000000..51785c1 --- /dev/null +++ b/public/icons/logo.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/maskable.svg b/public/icons/maskable.svg new file mode 100644 index 0000000..c2e4adb --- /dev/null +++ b/public/icons/maskable.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..7f18082 --- /dev/null +++ b/public/index.html @@ -0,0 +1,34 @@ + + + + + + + Premier Gunner + + + + + + +

+ +

Premier Gunner

+ +
+ +
+ + + + + + + + + diff --git a/public/js/api.js b/public/js/api.js new file mode 100644 index 0000000..8ea9dad --- /dev/null +++ b/public/js/api.js @@ -0,0 +1,46 @@ +// Thin fetch wrapper. Redirects to login on 401. +async function req(method, url, body) { + const opts = { method, headers: {} }; + if (body !== undefined) { + opts.headers['Content-Type'] = 'application/json'; + opts.body = JSON.stringify(body); + } + const res = await fetch(url, opts); + if (res.status === 401 && !url.endsWith('/api/login')) { + location.href = '/login.html'; + throw new Error('Not authenticated'); + } + const data = res.headers.get('content-type')?.includes('application/json') + ? await res.json() : null; + if (!res.ok) throw new Error((data && data.error) || `Request failed (${res.status})`); + return data; +} + +export const api = { + login: (password) => req('POST', '/api/login', { password }), + logout: () => req('POST', '/api/logout'), + me: () => req('GET', '/api/me'), + setPassword: (current, next) => req('POST', '/api/password', { current, next }), + + categories: (all = false) => req('GET', `/api/categories${all ? '?all=1' : ''}`), + addCategory: (c) => req('POST', '/api/categories', c), + updateCategory: (id, c) => req('PUT', `/api/categories/${id}`, c), + addMetric: (catId, m) => req('POST', `/api/categories/${catId}/metrics`, m), + deleteMetric: (id) => req('DELETE', `/api/metrics/${id}`), + + day: (day) => req('GET', `/api/day/${day}`), + logEntry: (e) => req('POST', '/api/entries', e), + deleteEntry: (id) => req('DELETE', `/api/entries/${id}`), + saveNotes: (day, notes) => req('PUT', `/api/day/${day}/notes`, { notes }), + + plans: (from, to) => req('GET', `/api/plans?from=${from}&to=${to}`), + addPlan: (p) => req('POST', '/api/plans', p), + deletePlan: (id) => req('DELETE', `/api/plans/${id}`), + + goals: () => req('GET', '/api/goals'), + addGoal: (g) => req('POST', '/api/goals', g), + updateGoal: (id, g) => req('PUT', `/api/goals/${id}`, g), + deleteGoal: (id) => req('DELETE', `/api/goals/${id}`), + + stats: () => req('GET', '/api/stats'), +}; diff --git a/public/js/app.js b/public/js/app.js new file mode 100644 index 0000000..20ab3e8 --- /dev/null +++ b/public/js/app.js @@ -0,0 +1,366 @@ +import { api } from '/js/api.js'; +import { renderStats } from '/js/dashboard.js'; + +// ---------- tiny DOM + date helpers ---------- +export const h = (tag, attrs = {}, ...kids) => { + const e = document.createElement(tag); + for (const [k, v] of Object.entries(attrs)) { + if (k === 'class') e.className = v; + else if (k === 'html') e.innerHTML = v; + else if (k.startsWith('on') && typeof v === 'function') e.addEventListener(k.slice(2), v); + else if (v === true) e.setAttribute(k, ''); + else if (v !== false && v != null) e.setAttribute(k, v); + } + for (const kid of kids.flat()) { + if (kid == null || kid === false) continue; + e.append(kid.nodeType ? kid : document.createTextNode(kid)); + } + return e; +}; + +const pad = (n) => String(n).padStart(2, '0'); +export const isoOf = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; +export const todayISO = () => isoOf(new Date()); +export const parseISO = (s) => { const [y, m, d] = s.split('-').map(Number); return new Date(y, m - 1, d); }; +export const addDays = (iso, n) => { const d = parseISO(iso); d.setDate(d.getDate() + n); return isoOf(d); }; +const WEEK = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; +const MON = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; +export const prettyDay = (iso) => { + const d = parseISO(iso); const t = todayISO(); + if (iso === t) return `Today · ${WEEK[d.getDay()]} ${MON[d.getMonth()]} ${d.getDate()}`; + if (iso === addDays(t, -1)) return `Yesterday · ${MON[d.getMonth()]} ${d.getDate()}`; + if (iso === addDays(t, 1)) return `Tomorrow · ${MON[d.getMonth()]} ${d.getDate()}`; + return `${WEEK[d.getDay()]} · ${MON[d.getMonth()]} ${d.getDate()}`; +}; + +export function toast(msg) { + document.querySelector('.toast')?.remove(); + const t = h('div', { class: 'toast' }, msg); + document.body.append(t); + setTimeout(() => t.remove(), 1900); +} + +const modalRoot = document.getElementById('modal-root'); +export function openModal(title, contentEl, { onClose } = {}) { + closeModal(); + const close = () => { modalRoot.innerHTML = ''; onClose?.(); }; + const backdrop = h('div', { class: 'modal-backdrop', onclick: (e) => { if (e.target === backdrop) close(); } }, + h('div', { class: 'modal' }, + h('h2', {}, title, h('button', { class: 'close', onclick: close }, '✕')), + contentEl)); + modalRoot.append(backdrop); + return close; +} +export const closeModal = () => { modalRoot.innerHTML = ''; }; + +const textColorFor = (hex) => { + const c = hex.replace('#', ''); + const r = parseInt(c.substr(0, 2), 16), g = parseInt(c.substr(2, 2), 16), b = parseInt(c.substr(4, 2), 16); + return (r * 299 + g * 587 + b * 114) / 1000 > 150 ? '#15181f' : '#fff'; +}; + +// ---------- state ---------- +const state = { categories: [], day: todayISO(), tab: 'today' }; +const catById = (id) => state.categories.find((c) => c.id === id); + +async function loadCategories() { state.categories = await api.categories(); } + +// ---------- TODAY (logging) ---------- +async function renderToday(view) { + const data = await api.day(state.day); + const loggedCatIds = new Set(data.entries.map((e) => e.category_id)); + const plannedIds = new Set(data.plans.map((p) => p.category_id)); + + view.innerHTML = ''; + view.append( + h('div', { class: 'datenav' }, + h('button', { onclick: () => { state.day = addDays(state.day, -1); renderToday(view); } }, '‹'), + h('div', { class: 'day-label' }, prettyDay(state.day)), + h('button', { onclick: () => { state.day = addDays(state.day, 1); renderToday(view); } }, '›'))); + + if (plannedIds.size) { + view.append(h('div', { class: 'card' }, + h('h2', {}, '🗓️ Today\'s plan'), + h('div', { class: 'pill-grid' }, + [...plannedIds].map((id) => { + const c = catById(id); if (!c) return null; + return h('span', { class: 'pill' + (loggedCatIds.has(id) ? ' done' : '') }, + h('span', { class: 'emoji' }, c.emoji), c.name, loggedCatIds.has(id) ? ' ✅' : ''); + })))); + } + + view.append(h('div', { class: 'card' }, + h('h2', {}, '⚽ What did you train?'), + h('p', { class: 'muted small' }, 'Tap a category to log it.'), + h('div', { class: 'pill-grid' }, + state.categories.map((c) => { + const done = loggedCatIds.has(c.id); + const style = `background:${c.color};border-color:${c.color};color:${textColorFor(c.color)}`; + return h('button', { + class: 'pill' + (done ? ' done' : ''), + style: done ? style : '', + onclick: () => openLogModal(c, view), + }, h('span', { class: 'emoji' }, c.emoji), c.name, done ? ' ✅' : ''); + })))); + + // Logged entries for the day + const log = h('div', { class: 'card' }, h('h2', {}, '✅ Logged')); + if (!data.entries.length) { + log.append(h('p', { class: 'muted' }, 'Nothing yet — tap a category above to start!')); + } else { + for (const e of data.entries) { + const c = catById(e.category_id) || { emoji: '⚽', name: 'Category', metrics: [] }; + const valStr = e.values.map((v) => { + const m = c.metrics?.find((mm) => mm.id === v.metric_id); + return m ? `${v.value} ${m.unit || m.name}` : v.value; + }).join(' · '); + log.append(h('div', { class: 'entry' }, + h('span', { class: 'emoji' }, c.emoji), + h('div', { style: 'flex:1' }, h('div', {}, c.name), valStr && h('div', { class: 'vals' }, valStr)), + h('button', { class: 'btn-danger', onclick: async () => { await api.deleteEntry(e.id); renderToday(view); refreshStatsIfActive(); } }, '🗑'))); + } + } + view.append(log); + + // Daily notes + const ta = h('textarea', { rows: 3, placeholder: 'Notes, thoughts, things to remember…' }, data.notes || ''); + view.append(h('div', { class: 'card' }, + h('h2', {}, '📝 Notes for the day'), + ta, + h('div', { class: 'btn-row' }, + h('button', { class: 'btn-primary', onclick: async () => { await api.saveNotes(state.day, ta.value); toast('Notes saved'); } }, 'Save notes')))); +} + +function openLogModal(cat, view) { + const metrics = cat.metrics || []; + const values = {}; + const body = h('div', {}); + body.append(h('p', { class: 'muted' }, `${cat.emoji} ${cat.name}`)); + if (!metrics.length) body.append(h('p', { class: 'muted small' }, 'No metrics — this just logs a session.')); + for (const m of metrics) { + values[m.id] = 0; + const valEl = h('span', { class: 'val' }, '0'); + const set = (n) => { values[m.id] = Math.max(0, n); valEl.textContent = values[m.id]; }; + body.append(h('div', { class: 'metric' }, + h('label', {}, m.name), + h('div', { class: 'stepper' }, + h('button', { onclick: () => set(values[m.id] - (m.step || 1)) }, '−'), + valEl, + h('button', { onclick: () => set(values[m.id] + (m.step || 1)) }, '+'), + h('span', { class: 'unit' }, m.unit || '')))); + } + body.append(h('div', { class: 'btn-row' }, + h('button', { class: 'btn-primary big', onclick: async () => { + await api.logEntry({ + day: state.day, category_id: cat.id, + values: metrics.map((m) => ({ metric_id: m.id, value: values[m.id] })), + }); + closeModal(); + toast(`${cat.emoji} ${cat.name} logged!`); + renderToday(view); + refreshStatsIfActive(); + } }, 'Log it! 🎉'))); + openModal(`Log ${cat.name}`, body); +} + +// ---------- PLAN ---------- +async function renderPlan(view) { + const from = todayISO(); const to = addDays(from, 13); + const plans = await api.plans(from, to); + const byDay = {}; + for (const p of plans) (byDay[p.day] ||= []).push(p); + + view.innerHTML = ''; + view.append(h('div', { class: 'card' }, + h('h2', {}, '🗓️ Plan your week'), + h('p', { class: 'muted small' }, 'Pick what to work on each day. You can always change your mind!'))); + + for (let i = 0; i < 14; i++) { + const day = addDays(from, i); + const dayPlans = byDay[day] || []; + const plannedIds = new Set(dayPlans.map((p) => p.category_id)); + view.append(h('div', { class: 'card' }, + h('div', { class: 'row spread' }, + h('strong', {}, prettyDay(day)), + h('span', { class: 'badge' }, `${plannedIds.size} planned`)), + h('div', { class: 'pill-grid', style: 'margin-top:10px' }, + state.categories.map((c) => { + const on = plannedIds.has(c.id); + const style = on ? `background:${c.color};border-color:${c.color};color:${textColorFor(c.color)}` : ''; + return h('button', { class: 'pill' + (on ? ' selected' : ''), style, onclick: async () => { + if (on) { + const p = dayPlans.find((x) => x.category_id === c.id); + await api.deletePlan(p.id); + } else { + await api.addPlan({ day, category_id: c.id }); + } + renderPlan(view); + } }, h('span', { class: 'emoji' }, c.emoji), c.name); + })))); + } +} + +// ---------- GOALS ---------- +async function renderGoals(view) { + const stats = await api.stats(); + view.innerHTML = ''; + view.append(h('div', { class: 'row spread' }, + h('h2', {}, '🏆 Goals'), + h('button', { class: 'btn-primary', onclick: () => openGoalModal(view) }, '+ New goal'))); + + if (!stats.goals.length) { + view.append(h('div', { class: 'empty' }, h('span', { class: 'big-emoji' }, '🎯'), 'No goals yet. Add one to chase!')); + return; + } + for (const g of stats.goals) { + view.append(goalCard(g, view)); + } +} + +function goalCard(g, view) { + const done = g.pct >= 100; + const catName = g.category_id ? (catById(g.category_id)?.name || '') : 'Overall'; + return h('div', { class: 'card goal' }, + h('div', { class: 'top' }, + h('span', { class: 'label' }, (g.is_main ? '⭐ ' : '') + (g.label || catName)), + h('span', { class: 'muted small' }, `${Math.round(g.current)} / ${g.target}`)), + h('div', { class: 'muted small' }, `${catName} · ${kindLabel(g.kind)}${g.reward ? ' · 🎁 ' + g.reward : ''}`), + h('div', { class: 'bar' + (done ? ' done' : '') }, h('span', { style: `width:${g.pct}%` })), + h('div', { class: 'btn-row' }, + h('button', { class: 'btn-danger', onclick: async () => { if (confirm('Delete this goal?')) { await api.deleteGoal(g.id); renderGoals(view); } } }, 'Delete'))); +} + +const kindLabel = (k) => ({ session_count: 'Times trained', metric_best: 'Personal best', metric_total: 'Total amount' }[k] || k); + +function openGoalModal(view) { + const body = h('div', {}); + const labelIn = h('input', { placeholder: 'Goal name (e.g. Juggle 100 in a row)' }); + const scopeSel = h('select', {}, h('option', { value: 'category' }, 'A category'), h('option', { value: 'overall' }, 'Overall (all training)')); + const catSel = h('select', {}, state.categories.map((c) => h('option', { value: c.id }, `${c.emoji} ${c.name}`))); + const kindSel = h('select', {}, + h('option', { value: 'session_count' }, 'Number of times trained'), + h('option', { value: 'metric_best' }, 'Personal best (one session)'), + h('option', { value: 'metric_total' }, 'Total added up')); + const metricSel = h('select', {}); + const targetIn = h('input', { type: 'number', min: '1', placeholder: 'Target number' }); + const rewardIn = h('input', { placeholder: 'Reward (optional)' }); + const mainChk = h('input', { type: 'checkbox' }); + + const catField = h('div', { class: 'field' }, h('label', {}, 'Category'), catSel); + const metricField = h('div', { class: 'field' }, h('label', {}, 'Which metric?'), metricSel); + + const refreshMetrics = () => { + const c = catById(Number(catSel.value)); + metricSel.innerHTML = ''; + (c?.metrics || []).forEach((m) => metricSel.append(h('option', { value: m.id }, `${m.name} (${m.unit || ''})`))); + }; + const refreshVis = () => { + catField.style.display = scopeSel.value === 'category' ? '' : 'none'; + const needMetric = kindSel.value !== 'session_count' && scopeSel.value === 'category'; + metricField.style.display = needMetric ? '' : 'none'; + if (scopeSel.value === 'overall') kindSel.value = 'session_count'; + }; + scopeSel.addEventListener('change', refreshVis); + kindSel.addEventListener('change', refreshVis); + catSel.addEventListener('change', refreshMetrics); + refreshMetrics(); refreshVis(); + + body.append( + h('div', { class: 'field' }, h('label', {}, 'Goal name'), labelIn), + h('div', { class: 'field' }, h('label', {}, 'Track…'), scopeSel), + catField, + h('div', { class: 'field' }, h('label', {}, 'Goal type'), kindSel), + metricField, + h('div', { class: 'field' }, h('label', {}, 'Target'), targetIn), + h('div', { class: 'field' }, h('label', {}, 'Reward'), rewardIn), + h('label', { class: 'row', style: 'gap:10px' }, mainChk, ' Make this the ⭐ main goal (thermometer)'), + h('div', { class: 'btn-row' }, + h('button', { class: 'btn-primary big', onclick: async () => { + const payload = { + label: labelIn.value, scope: scopeSel.value, kind: kindSel.value, + target: Number(targetIn.value), reward: rewardIn.value, is_main: mainChk.checked, + }; + if (scopeSel.value === 'category') payload.category_id = Number(catSel.value); + if (kindSel.value !== 'session_count') payload.metric_id = Number(metricSel.value); + try { await api.addGoal(payload); closeModal(); renderGoals(view); toast('Goal added 🏆'); } + catch (e) { alert(e.message); } + } }, 'Save goal'))); + openModal('New goal', body); +} + +// ---------- SETTINGS ---------- +function openSettings() { + const body = h('div', {}); + body.append(h('h3', {}, 'Categories')); + const list = h('div', {}); + const renderList = () => { + list.innerHTML = ''; + for (const c of state.categories) { + list.append(h('div', { class: 'entry' }, + h('span', { class: 'emoji' }, c.emoji), + h('div', { style: 'flex:1' }, c.name, + h('div', { class: 'vals' }, (c.metrics || []).map((m) => m.name).join(', '))), + h('button', { class: 'btn-danger', onclick: async () => { await api.updateCategory(c.id, { archived: 1 }); await loadCategories(); renderList(); refreshActive(); } }, 'Archive'))); + } + }; + renderList(); + body.append(list); + + // Add category form + const nameIn = h('input', { placeholder: 'New category name' }); + const emojiIn = h('input', { placeholder: 'Emoji', value: '⚽', maxlength: '4', style: 'max-width:90px' }); + const colorIn = h('input', { type: 'color', value: '#EF0107', style: 'max-width:60px;height:48px;padding:4px' }); + const metricName = h('input', { placeholder: 'Metric (e.g. Minutes)', value: 'Minutes' }); + const metricKind = h('select', {}, h('option', { value: 'duration' }, 'Time'), h('option', { value: 'count' }, 'Count'), h('option', { value: 'score' }, 'Score')); + body.append(h('div', { class: 'card', style: 'margin-top:16px' }, + h('h3', {}, '➕ Add a category'), + h('div', { class: 'field' }, h('label', {}, 'Name'), nameIn), + h('div', { class: 'row', style: 'gap:10px' }, emojiIn, colorIn), + h('div', { class: 'field', style: 'margin-top:10px' }, h('label', {}, 'First metric'), h('div', { class: 'row', style: 'gap:10px' }, metricName, metricKind)), + h('button', { class: 'btn-primary', onclick: async () => { + if (!nameIn.value.trim()) return alert('Enter a name'); + await api.addCategory({ name: nameIn.value.trim(), emoji: emojiIn.value || '⚽', color: colorIn.value, + metrics: [{ name: metricName.value || 'Value', unit: metricKind.value === 'duration' ? 'min' : '', kind: metricKind.value, step: metricKind.value === 'duration' ? 5 : 1 }] }); + await loadCategories(); renderList(); nameIn.value = ''; refreshActive(); toast('Category added'); + } }, 'Add category'))); + + // Password change + const cur = h('input', { type: 'password', placeholder: 'Current password' }); + const nxt = h('input', { type: 'password', placeholder: 'New password' }); + body.append(h('div', { class: 'card' }, + h('h3', {}, '🔒 Change password'), + h('div', { class: 'field' }, cur), h('div', { class: 'field' }, nxt), + h('button', { class: 'btn-ghost', onclick: async () => { + try { await api.setPassword(cur.value, nxt.value); toast('Password changed'); cur.value = nxt.value = ''; } + catch (e) { alert(e.message); } + } }, 'Update password'))); + + body.append(h('button', { class: 'btn-danger', style: 'margin-top:8px', onclick: async () => { await api.logout(); location.href = '/login.html'; } }, 'Log out')); + openModal('⚙️ Settings', body, { onClose: refreshActive }); +} + +// ---------- router ---------- +function refreshActive() { switchTab(state.tab); } +function refreshStatsIfActive() { if (state.tab === 'stats') switchTab('stats'); } + +const view = document.getElementById('view'); +function switchTab(tab) { + state.tab = tab; + document.querySelectorAll('.tab').forEach((t) => t.classList.toggle('active', t.dataset.tab === tab)); + view.innerHTML = '

Loading…

'; + if (tab === 'today') renderToday(view); + else if (tab === 'plan') renderPlan(view); + else if (tab === 'stats') renderStats(view, { catById }); + else if (tab === 'goals') renderGoals(view); +} + +document.querySelectorAll('.tab').forEach((t) => t.addEventListener('click', () => switchTab(t.dataset.tab))); +document.getElementById('settings-btn').addEventListener('click', openSettings); + +// ---------- boot ---------- +(async () => { + try { await api.me(); } catch { return; } + await loadCategories(); + switchTab('today'); + if ('serviceWorker' in navigator) navigator.serviceWorker.register('/sw.js').catch(() => {}); +})(); diff --git a/public/js/dashboard.js b/public/js/dashboard.js new file mode 100644 index 0000000..aaff6d3 --- /dev/null +++ b/public/js/dashboard.js @@ -0,0 +1,172 @@ +import { api } from '/js/api.js'; +import { h, parseISO, isoOf, todayISO } from '/js/app.js'; + +let radarChart = null; +let lineChart = null; + +const RED = '#EF0107'; + +export async function renderStats(view, { catById }) { + const stats = await api.stats(); + if (radarChart) { radarChart.destroy(); radarChart = null; } + if (lineChart) { lineChart.destroy(); lineChart = null; } + view.innerHTML = ''; + + // ---- top numbers ---- + view.append(h('div', { class: 'stat-grid' }, + stat(stats.totalSessions, 'Sessions'), + stat(stats.totalDays, 'Training days'), + stat(`${stats.current}🔥`, 'Day streak'), + stat(stats.longest, 'Best streak'))); + + // ---- main goal thermometer ---- + const main = stats.goals.find((g) => g.is_main); + if (main) view.append(thermometer(main)); + + // ---- heatmap ---- + view.append(h('div', { class: 'card' }, + h('h2', {}, '🔥 Training calendar'), + h('div', { class: 'heatmap' }, heatmapSvg(stats.heatmap)), + h('p', { class: 'muted small' }, 'Each square is a day. Brighter red = more training!'))); + + // ---- radar ---- + const radarData = stats.radar.filter((r) => true); + if (radarData.length >= 3) { + const canvas = h('canvas', { height: 280 }); + view.append(h('div', { class: 'card' }, h('h2', {}, '🕸️ Training spread'), canvas)); + radarChart = new Chart(canvas, { + type: 'radar', + data: { + labels: radarData.map((r) => r.name), + datasets: [{ + label: 'Sessions', data: radarData.map((r) => r.sessions), + backgroundColor: 'rgba(239,1,7,.18)', borderColor: RED, borderWidth: 2, + pointBackgroundColor: RED, + }], + }, + options: { + plugins: { legend: { display: false } }, + scales: { r: { beginAtZero: true, ticks: { precision: 0 }, pointLabels: { font: { size: 11 } } } }, + }, + }); + } + + // ---- line chart with metric picker ---- + const metrics = []; + for (const c of stats.radar.map((r) => catById(r.category_id)).filter(Boolean)) { + for (const m of c.metrics || []) { + if (stats.series[m.id]?.length) metrics.push({ ...m, catName: c.name, emoji: c.emoji }); + } + } + if (metrics.length) { + const sel = h('select', {}, metrics.map((m) => h('option', { value: m.id }, `${m.emoji} ${m.catName} — ${m.name}`))); + const canvas = h('canvas', { height: 260 }); + const draw = () => { + const m = metrics.find((x) => x.id === Number(sel.value)); + const pts = stats.series[m.id] || []; + if (lineChart) lineChart.destroy(); + lineChart = new Chart(canvas, { + type: 'line', + data: { + labels: pts.map((p) => p.day.slice(5)), + datasets: [{ + label: `${m.name}${m.unit ? ' (' + m.unit + ')' : ''}`, + data: pts.map((p) => p.value), + borderColor: RED, backgroundColor: 'rgba(239,1,7,.12)', + fill: true, tension: .3, pointRadius: 4, pointBackgroundColor: RED, + }], + }, + options: { plugins: { legend: { display: true } }, scales: { y: { beginAtZero: true } } }, + }); + }; + sel.addEventListener('change', draw); + view.append(h('div', { class: 'card' }, + h('h2', {}, '📈 Improvement over time'), + h('div', { class: 'field' }, sel), + canvas)); + draw(); + } + + // ---- all goals ---- + if (stats.goals.length) { + const card = h('div', { class: 'card' }, h('h2', {}, '🏆 Goal progress')); + for (const g of stats.goals) { + const done = g.pct >= 100; + const catName = g.category_id ? (catById(g.category_id)?.name || '') : 'Overall'; + card.append(h('div', { class: 'goal' }, + h('div', { class: 'top' }, + h('span', { class: 'label' }, (g.is_main ? '⭐ ' : '') + (g.label || catName)), + h('span', { class: 'muted small' }, `${Math.round(g.current)} / ${g.target} (${g.pct}%)`)), + h('div', { class: 'bar' + (done ? ' done' : '') }, h('span', { style: `width:${g.pct}%` })))); + } + view.append(card); + } +} + +const stat = (num, lbl) => h('div', { class: 'stat' }, h('div', { class: 'num' }, String(num)), h('div', { class: 'lbl' }, lbl)); + +function thermometer(g) { + const pct = g.pct; + const tubeH = 180, fillH = Math.round((tubeH - 8) * pct / 100); + const svgNS = 'http://www.w3.org/2000/svg'; + const svg = document.createElementNS(svgNS, 'svg'); + svg.setAttribute('viewBox', '0 0 46 200'); svg.setAttribute('width', '46'); svg.setAttribute('height', '200'); + svg.innerHTML = ` + + + + `; + return h('div', { class: 'card' }, + h('h2', {}, '✈️ ' + (g.label || 'Main goal')), + h('div', { class: 'thermo-wrap' }, + svg, + h('div', { class: 'reward' }, + h('div', { class: 'pct' }, `${pct}%`), + h('div', {}, `${Math.round(g.current)} of ${g.target}`), + g.reward && h('p', { class: 'muted', style: 'margin-top:8px' }, '🎁 ' + g.reward)))); +} + +function heatmapSvg(rows) { + const counts = new Map(rows.map((r) => [r.day, r.count])); + const max = Math.max(1, ...rows.map((r) => r.count)); + const weeks = 27, cell = 14, gap = 3; + const end = parseISO(todayISO()); + const start = new Date(end); start.setDate(start.getDate() - weeks * 7); + // back up to Sunday + start.setDate(start.getDate() - start.getDay()); + + const shade = (n) => { + if (!n) return '#ebedf3'; + const t = n / max; // 0..1 + const light = 88 - Math.round(t * 50); // 88% -> 38% + return `hsl(2 90% ${light}%)`; + }; + + const svgNS = 'http://www.w3.org/2000/svg'; + const w = weeks * (cell + gap) + 24, hgt = 7 * (cell + gap) + 16; + const svg = document.createElementNS(svgNS, 'svg'); + svg.setAttribute('viewBox', `0 0 ${w} ${hgt}`); + svg.setAttribute('width', w); svg.setAttribute('height', hgt); + + let parts = ''; + const MON = ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D']; + const d = new Date(start); + let lastMonth = -1; + for (let col = 0; col <= weeks; col++) { + for (let r = 0; r < 7; r++) { + const iso = isoOf(d); + if (d <= end) { + const x = col * (cell + gap); + const y = r * (cell + gap) + 12; + parts += `${iso}: ${counts.get(iso) || 0}`; + if (r === 0 && d.getMonth() !== lastMonth) { + parts += `${MON[d.getMonth()]}`; + lastMonth = d.getMonth(); + } + } + d.setDate(d.getDate() + 1); + } + } + svg.innerHTML = parts; + return svg; +} diff --git a/public/login.html b/public/login.html new file mode 100644 index 0000000..897f7b1 --- /dev/null +++ b/public/login.html @@ -0,0 +1,41 @@ + + + + + + + Premier Gunner — Login + + + + + + +
+ +

Premier Gunner

+

Train. Track. Road to London. ✈️

+
+ + + +
+
+ + + diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest new file mode 100644 index 0000000..3a4e0bb --- /dev/null +++ b/public/manifest.webmanifest @@ -0,0 +1,16 @@ +{ + "name": "Premier Gunner", + "short_name": "Gunner", + "description": "Soccer training tracker — train, track, and chase the Road to London.", + "start_url": "/", + "scope": "/", + "display": "standalone", + "orientation": "portrait", + "background_color": "#EF0107", + "theme_color": "#EF0107", + "icons": [ + { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" }, + { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }, + { "src": "/icons/icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } + ] +} diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..b518338 --- /dev/null +++ b/public/sw.js @@ -0,0 +1,41 @@ +const CACHE = 'premier-gunner-v1'; +const SHELL = [ + '/', '/index.html', '/login.html', + '/css/styles.css', + '/js/app.js', '/js/api.js', '/js/dashboard.js', + '/vendor/chart.umd.min.js', + '/manifest.webmanifest', + '/icons/logo.svg', '/icons/favicon.svg', + '/icons/icon-192.png', '/icons/icon-512.png', +]; + +self.addEventListener('install', (e) => { + e.waitUntil(caches.open(CACHE).then((c) => c.addAll(SHELL)).then(() => self.skipWaiting())); +}); + +self.addEventListener('activate', (e) => { + e.waitUntil( + caches.keys().then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))) + .then(() => self.clients.claim()) + ); +}); + +self.addEventListener('fetch', (e) => { + const url = new URL(e.request.url); + if (e.request.method !== 'GET') return; + // Never cache the API — always go to network so data stays fresh and auth works. + if (url.pathname.startsWith('/api/')) return; + + e.respondWith( + caches.match(e.request).then((cached) => { + const network = fetch(e.request).then((res) => { + if (res.ok && url.origin === location.origin) { + const copy = res.clone(); + caches.open(CACHE).then((c) => c.put(e.request, copy)); + } + return res; + }).catch(() => cached); + return cached || network; + }) + ); +}); diff --git a/public/vendor/chart.umd.min.js b/public/vendor/chart.umd.min.js new file mode 100644 index 0000000..4c59ad2 --- /dev/null +++ b/public/vendor/chart.umd.min.js @@ -0,0 +1,20 @@ +/** + * Skipped minification because the original files appears to be already minified. + * Original file: /npm/chart.js@4.4.6/dist/chart.umd.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +/*! + * Chart.js v4.4.6 + * https://www.chartjs.org + * (c) 2024 Chart.js Contributors + * Released under the MIT License + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Chart=e()}(this,(function(){"use strict";var t=Object.freeze({__proto__:null,get Colors(){return Go},get Decimation(){return Qo},get Filler(){return ma},get Legend(){return ya},get SubTitle(){return ka},get Title(){return Ma},get Tooltip(){return Ba}});function e(){}const i=(()=>{let t=0;return()=>t++})();function s(t){return null==t}function n(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return"[object"===e.slice(0,7)&&"Array]"===e.slice(-6)}function o(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)}function a(t){return("number"==typeof t||t instanceof Number)&&isFinite(+t)}function r(t,e){return a(t)?t:e}function l(t,e){return void 0===t?e:t}const h=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100:+t/e,c=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100*e:+t;function d(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)}function u(t,e,i,s){let a,r,l;if(n(t))if(r=t.length,s)for(a=r-1;a>=0;a--)e.call(i,t[a],a);else for(a=0;at,x:t=>t.x,y:t=>t.y};function v(t){const e=t.split("."),i=[];let s="";for(const t of e)s+=t,s.endsWith("\\")?s=s.slice(0,-1)+".":(i.push(s),s="");return i}function M(t,e){const i=y[e]||(y[e]=function(t){const e=v(t);return t=>{for(const i of e){if(""===i)break;t=t&&t[i]}return t}}(e));return i(t)}function w(t){return t.charAt(0).toUpperCase()+t.slice(1)}const k=t=>void 0!==t,S=t=>"function"==typeof t,P=(t,e)=>{if(t.size!==e.size)return!1;for(const i of t)if(!e.has(i))return!1;return!0};function D(t){return"mouseup"===t.type||"click"===t.type||"contextmenu"===t.type}const C=Math.PI,O=2*C,A=O+C,T=Number.POSITIVE_INFINITY,L=C/180,E=C/2,R=C/4,I=2*C/3,z=Math.log10,F=Math.sign;function V(t,e,i){return Math.abs(t-e)t-e)).pop(),e}function N(t){return!isNaN(parseFloat(t))&&isFinite(t)}function H(t,e){const i=Math.round(t);return i-e<=t&&i+e>=t}function j(t,e,i){let s,n,o;for(s=0,n=t.length;sl&&h=Math.min(e,i)-s&&t<=Math.max(e,i)+s}function et(t,e,i){i=i||(i=>t[i]1;)s=o+n>>1,i(s)?o=s:n=s;return{lo:o,hi:n}}const it=(t,e,i,s)=>et(t,i,s?s=>{const n=t[s][e];return nt[s][e]et(t,i,(s=>t[s][e]>=i));function nt(t,e,i){let s=0,n=t.length;for(;ss&&t[n-1]>i;)n--;return s>0||n{const i="_onData"+w(e),s=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value(...e){const n=s.apply(this,e);return t._chartjs.listeners.forEach((t=>{"function"==typeof t[i]&&t[i](...e)})),n}})})))}function rt(t,e){const i=t._chartjs;if(!i)return;const s=i.listeners,n=s.indexOf(e);-1!==n&&s.splice(n,1),s.length>0||(ot.forEach((e=>{delete t[e]})),delete t._chartjs)}function lt(t){const e=new Set(t);return e.size===t.length?t:Array.from(e)}const ht="undefined"==typeof window?function(t){return t()}:window.requestAnimationFrame;function ct(t,e){let i=[],s=!1;return function(...n){i=n,s||(s=!0,ht.call(window,(()=>{s=!1,t.apply(e,i)})))}}function dt(t,e){let i;return function(...s){return e?(clearTimeout(i),i=setTimeout(t,e,s)):t.apply(this,s),e}}const ut=t=>"start"===t?"left":"end"===t?"right":"center",ft=(t,e,i)=>"start"===t?e:"end"===t?i:(e+i)/2,gt=(t,e,i,s)=>t===(s?"left":"right")?i:"center"===t?(e+i)/2:e;function pt(t,e,i){const s=e.length;let n=0,o=s;if(t._sorted){const{iScale:a,_parsed:r}=t,l=a.axis,{min:h,max:c,minDefined:d,maxDefined:u}=a.getUserBounds();d&&(n=J(Math.min(it(r,l,h).lo,i?s:it(e,l,a.getPixelForValue(h)).lo),0,s-1)),o=u?J(Math.max(it(r,a.axis,c,!0).hi+1,i?0:it(e,l,a.getPixelForValue(c),!0).hi+1),n,s)-n:s-n}return{start:n,count:o}}function mt(t){const{xScale:e,yScale:i,_scaleRanges:s}=t,n={xmin:e.min,xmax:e.max,ymin:i.min,ymax:i.max};if(!s)return t._scaleRanges=n,!0;const o=s.xmin!==e.min||s.xmax!==e.max||s.ymin!==i.min||s.ymax!==i.max;return Object.assign(s,n),o}class bt{constructor(){this._request=null,this._charts=new Map,this._running=!1,this._lastDate=void 0}_notify(t,e,i,s){const n=e.listeners[s],o=e.duration;n.forEach((s=>s({chart:t,initial:e.initial,numSteps:o,currentStep:Math.min(i-e.start,o)})))}_refresh(){this._request||(this._running=!0,this._request=ht.call(window,(()=>{this._update(),this._request=null,this._running&&this._refresh()})))}_update(t=Date.now()){let e=0;this._charts.forEach(((i,s)=>{if(!i.running||!i.items.length)return;const n=i.items;let o,a=n.length-1,r=!1;for(;a>=0;--a)o=n[a],o._active?(o._total>i.duration&&(i.duration=o._total),o.tick(t),r=!0):(n[a]=n[n.length-1],n.pop());r&&(s.draw(),this._notify(s,i,t,"progress")),n.length||(i.running=!1,this._notify(s,i,t,"complete"),i.initial=!1),e+=n.length})),this._lastDate=t,0===e&&(this._running=!1)}_getAnims(t){const e=this._charts;let i=e.get(t);return i||(i={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},e.set(t,i)),i}listen(t,e,i){this._getAnims(t).listeners[e].push(i)}add(t,e){e&&e.length&&this._getAnims(t).items.push(...e)}has(t){return this._getAnims(t).items.length>0}start(t){const e=this._charts.get(t);e&&(e.running=!0,e.start=Date.now(),e.duration=e.items.reduce(((t,e)=>Math.max(t,e._duration)),0),this._refresh())}running(t){if(!this._running)return!1;const e=this._charts.get(t);return!!(e&&e.running&&e.items.length)}stop(t){const e=this._charts.get(t);if(!e||!e.items.length)return;const i=e.items;let s=i.length-1;for(;s>=0;--s)i[s].cancel();e.items=[],this._notify(t,e,Date.now(),"complete")}remove(t){return this._charts.delete(t)}}var xt=new bt; +/*! + * @kurkle/color v0.3.2 + * https://github.com/kurkle/color#readme + * (c) 2023 Jukka Kurkela + * Released under the MIT License + */function _t(t){return t+.5|0}const yt=(t,e,i)=>Math.max(Math.min(t,i),e);function vt(t){return yt(_t(2.55*t),0,255)}function Mt(t){return yt(_t(255*t),0,255)}function wt(t){return yt(_t(t/2.55)/100,0,1)}function kt(t){return yt(_t(100*t),0,100)}const St={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Pt=[..."0123456789ABCDEF"],Dt=t=>Pt[15&t],Ct=t=>Pt[(240&t)>>4]+Pt[15&t],Ot=t=>(240&t)>>4==(15&t);function At(t){var e=(t=>Ot(t.r)&&Ot(t.g)&&Ot(t.b)&&Ot(t.a))(t)?Dt:Ct;return t?"#"+e(t.r)+e(t.g)+e(t.b)+((t,e)=>t<255?e(t):"")(t.a,e):void 0}const Tt=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Lt(t,e,i){const s=e*Math.min(i,1-i),n=(e,n=(e+t/30)%12)=>i-s*Math.max(Math.min(n-3,9-n,1),-1);return[n(0),n(8),n(4)]}function Et(t,e,i){const s=(s,n=(s+t/60)%6)=>i-i*e*Math.max(Math.min(n,4-n,1),0);return[s(5),s(3),s(1)]}function Rt(t,e,i){const s=Lt(t,1,.5);let n;for(e+i>1&&(n=1/(e+i),e*=n,i*=n),n=0;n<3;n++)s[n]*=1-e-i,s[n]+=e;return s}function It(t){const e=t.r/255,i=t.g/255,s=t.b/255,n=Math.max(e,i,s),o=Math.min(e,i,s),a=(n+o)/2;let r,l,h;return n!==o&&(h=n-o,l=a>.5?h/(2-n-o):h/(n+o),r=function(t,e,i,s,n){return t===n?(e-i)/s+(e>16&255,o>>8&255,255&o]}return t}(),Ht.transparent=[0,0,0,0]);const e=Ht[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:4===e.length?e[3]:255}}const $t=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;const Yt=t=>t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055,Ut=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4);function Xt(t,e,i){if(t){let s=It(t);s[e]=Math.max(0,Math.min(s[e]+s[e]*i,0===e?360:1)),s=Ft(s),t.r=s[0],t.g=s[1],t.b=s[2]}}function qt(t,e){return t?Object.assign(e||{},t):t}function Kt(t){var e={r:0,g:0,b:0,a:255};return Array.isArray(t)?t.length>=3&&(e={r:t[0],g:t[1],b:t[2],a:255},t.length>3&&(e.a=Mt(t[3]))):(e=qt(t,{r:0,g:0,b:0,a:1})).a=Mt(e.a),e}function Gt(t){return"r"===t.charAt(0)?function(t){const e=$t.exec(t);let i,s,n,o=255;if(e){if(e[7]!==i){const t=+e[7];o=e[8]?vt(t):yt(255*t,0,255)}return i=+e[1],s=+e[3],n=+e[5],i=255&(e[2]?vt(i):yt(i,0,255)),s=255&(e[4]?vt(s):yt(s,0,255)),n=255&(e[6]?vt(n):yt(n,0,255)),{r:i,g:s,b:n,a:o}}}(t):Bt(t)}class Zt{constructor(t){if(t instanceof Zt)return t;const e=typeof t;let i;var s,n,o;"object"===e?i=Kt(t):"string"===e&&(o=(s=t).length,"#"===s[0]&&(4===o||5===o?n={r:255&17*St[s[1]],g:255&17*St[s[2]],b:255&17*St[s[3]],a:5===o?17*St[s[4]]:255}:7!==o&&9!==o||(n={r:St[s[1]]<<4|St[s[2]],g:St[s[3]]<<4|St[s[4]],b:St[s[5]]<<4|St[s[6]],a:9===o?St[s[7]]<<4|St[s[8]]:255})),i=n||jt(t)||Gt(t)),this._rgb=i,this._valid=!!i}get valid(){return this._valid}get rgb(){var t=qt(this._rgb);return t&&(t.a=wt(t.a)),t}set rgb(t){this._rgb=Kt(t)}rgbString(){return this._valid?(t=this._rgb)&&(t.a<255?`rgba(${t.r}, ${t.g}, ${t.b}, ${wt(t.a)})`:`rgb(${t.r}, ${t.g}, ${t.b})`):void 0;var t}hexString(){return this._valid?At(this._rgb):void 0}hslString(){return this._valid?function(t){if(!t)return;const e=It(t),i=e[0],s=kt(e[1]),n=kt(e[2]);return t.a<255?`hsla(${i}, ${s}%, ${n}%, ${wt(t.a)})`:`hsl(${i}, ${s}%, ${n}%)`}(this._rgb):void 0}mix(t,e){if(t){const i=this.rgb,s=t.rgb;let n;const o=e===n?.5:e,a=2*o-1,r=i.a-s.a,l=((a*r==-1?a:(a+r)/(1+a*r))+1)/2;n=1-l,i.r=255&l*i.r+n*s.r+.5,i.g=255&l*i.g+n*s.g+.5,i.b=255&l*i.b+n*s.b+.5,i.a=o*i.a+(1-o)*s.a,this.rgb=i}return this}interpolate(t,e){return t&&(this._rgb=function(t,e,i){const s=Ut(wt(t.r)),n=Ut(wt(t.g)),o=Ut(wt(t.b));return{r:Mt(Yt(s+i*(Ut(wt(e.r))-s))),g:Mt(Yt(n+i*(Ut(wt(e.g))-n))),b:Mt(Yt(o+i*(Ut(wt(e.b))-o))),a:t.a+i*(e.a-t.a)}}(this._rgb,t._rgb,e)),this}clone(){return new Zt(this.rgb)}alpha(t){return this._rgb.a=Mt(t),this}clearer(t){return this._rgb.a*=1-t,this}greyscale(){const t=this._rgb,e=_t(.3*t.r+.59*t.g+.11*t.b);return t.r=t.g=t.b=e,this}opaquer(t){return this._rgb.a*=1+t,this}negate(){const t=this._rgb;return t.r=255-t.r,t.g=255-t.g,t.b=255-t.b,this}lighten(t){return Xt(this._rgb,2,t),this}darken(t){return Xt(this._rgb,2,-t),this}saturate(t){return Xt(this._rgb,1,t),this}desaturate(t){return Xt(this._rgb,1,-t),this}rotate(t){return function(t,e){var i=It(t);i[0]=Vt(i[0]+e),i=Ft(i),t.r=i[0],t.g=i[1],t.b=i[2]}(this._rgb,t),this}}function Jt(t){if(t&&"object"==typeof t){const e=t.toString();return"[object CanvasPattern]"===e||"[object CanvasGradient]"===e}return!1}function Qt(t){return Jt(t)?t:new Zt(t)}function te(t){return Jt(t)?t:new Zt(t).saturate(.5).darken(.1).hexString()}const ee=["x","y","borderWidth","radius","tension"],ie=["color","borderColor","backgroundColor"];const se=new Map;function ne(t,e,i){return function(t,e){e=e||{};const i=t+JSON.stringify(e);let s=se.get(i);return s||(s=new Intl.NumberFormat(t,e),se.set(i,s)),s}(e,i).format(t)}const oe={values:t=>n(t)?t:""+t,numeric(t,e,i){if(0===t)return"0";const s=this.chart.options.locale;let n,o=t;if(i.length>1){const e=Math.max(Math.abs(i[0].value),Math.abs(i[i.length-1].value));(e<1e-4||e>1e15)&&(n="scientific"),o=function(t,e){let i=e.length>3?e[2].value-e[1].value:e[1].value-e[0].value;Math.abs(i)>=1&&t!==Math.floor(t)&&(i=t-Math.floor(t));return i}(t,i)}const a=z(Math.abs(o)),r=isNaN(a)?1:Math.max(Math.min(-1*Math.floor(a),20),0),l={notation:n,minimumFractionDigits:r,maximumFractionDigits:r};return Object.assign(l,this.options.ticks.format),ne(t,s,l)},logarithmic(t,e,i){if(0===t)return"0";const s=i[e].significand||t/Math.pow(10,Math.floor(z(t)));return[1,2,3,5,10,15].includes(s)||e>.8*i.length?oe.numeric.call(this,t,e,i):""}};var ae={formatters:oe};const re=Object.create(null),le=Object.create(null);function he(t,e){if(!e)return t;const i=e.split(".");for(let e=0,s=i.length;et.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(t,e)=>te(e.backgroundColor),this.hoverBorderColor=(t,e)=>te(e.borderColor),this.hoverColor=(t,e)=>te(e.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0,includeInvisible:!1},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.drawActiveElementsOnTop=!0,this.describe(t),this.apply(e)}set(t,e){return ce(this,t,e)}get(t){return he(this,t)}describe(t,e){return ce(le,t,e)}override(t,e){return ce(re,t,e)}route(t,e,i,s){const n=he(this,t),a=he(this,i),r="_"+e;Object.defineProperties(n,{[r]:{value:n[e],writable:!0},[e]:{enumerable:!0,get(){const t=this[r],e=a[s];return o(t)?Object.assign({},e,t):l(t,e)},set(t){this[r]=t}}})}apply(t){t.forEach((t=>t(this)))}}var ue=new de({_scriptable:t=>!t.startsWith("on"),_indexable:t=>"events"!==t,hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}},[function(t){t.set("animation",{delay:void 0,duration:1e3,easing:"easeOutQuart",fn:void 0,from:void 0,loop:void 0,to:void 0,type:void 0}),t.describe("animation",{_fallback:!1,_indexable:!1,_scriptable:t=>"onProgress"!==t&&"onComplete"!==t&&"fn"!==t}),t.set("animations",{colors:{type:"color",properties:ie},numbers:{type:"number",properties:ee}}),t.describe("animations",{_fallback:"animation"}),t.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:t=>0|t}}}})},function(t){t.set("layout",{autoPadding:!0,padding:{top:0,right:0,bottom:0,left:0}})},function(t){t.set("scale",{display:!0,offset:!1,reverse:!1,beginAtZero:!1,bounds:"ticks",clip:!0,grace:0,grid:{display:!0,lineWidth:1,drawOnChartArea:!0,drawTicks:!0,tickLength:8,tickWidth:(t,e)=>e.lineWidth,tickColor:(t,e)=>e.color,offset:!1},border:{display:!0,dash:[],dashOffset:0,width:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:ae.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}}),t.route("scale.ticks","color","","color"),t.route("scale.grid","color","","borderColor"),t.route("scale.border","color","","borderColor"),t.route("scale.title","color","","color"),t.describe("scale",{_fallback:!1,_scriptable:t=>!t.startsWith("before")&&!t.startsWith("after")&&"callback"!==t&&"parser"!==t,_indexable:t=>"borderDash"!==t&&"tickBorderDash"!==t&&"dash"!==t}),t.describe("scales",{_fallback:"scale"}),t.describe("scale.ticks",{_scriptable:t=>"backdropPadding"!==t&&"callback"!==t,_indexable:t=>"backdropPadding"!==t})}]);function fe(){return"undefined"!=typeof window&&"undefined"!=typeof document}function ge(t){let e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e}function pe(t,e,i){let s;return"string"==typeof t?(s=parseInt(t,10),-1!==t.indexOf("%")&&(s=s/100*e.parentNode[i])):s=t,s}const me=t=>t.ownerDocument.defaultView.getComputedStyle(t,null);function be(t,e){return me(t).getPropertyValue(e)}const xe=["top","right","bottom","left"];function _e(t,e,i){const s={};i=i?"-"+i:"";for(let n=0;n<4;n++){const o=xe[n];s[o]=parseFloat(t[e+"-"+o+i])||0}return s.width=s.left+s.right,s.height=s.top+s.bottom,s}const ye=(t,e,i)=>(t>0||e>0)&&(!i||!i.shadowRoot);function ve(t,e){if("native"in t)return t;const{canvas:i,currentDevicePixelRatio:s}=e,n=me(i),o="border-box"===n.boxSizing,a=_e(n,"padding"),r=_e(n,"border","width"),{x:l,y:h,box:c}=function(t,e){const i=t.touches,s=i&&i.length?i[0]:t,{offsetX:n,offsetY:o}=s;let a,r,l=!1;if(ye(n,o,t.target))a=n,r=o;else{const t=e.getBoundingClientRect();a=s.clientX-t.left,r=s.clientY-t.top,l=!0}return{x:a,y:r,box:l}}(t,i),d=a.left+(c&&r.left),u=a.top+(c&&r.top);let{width:f,height:g}=e;return o&&(f-=a.width+r.width,g-=a.height+r.height),{x:Math.round((l-d)/f*i.width/s),y:Math.round((h-u)/g*i.height/s)}}const Me=t=>Math.round(10*t)/10;function we(t,e,i,s){const n=me(t),o=_e(n,"margin"),a=pe(n.maxWidth,t,"clientWidth")||T,r=pe(n.maxHeight,t,"clientHeight")||T,l=function(t,e,i){let s,n;if(void 0===e||void 0===i){const o=t&&ge(t);if(o){const t=o.getBoundingClientRect(),a=me(o),r=_e(a,"border","width"),l=_e(a,"padding");e=t.width-l.width-r.width,i=t.height-l.height-r.height,s=pe(a.maxWidth,o,"clientWidth"),n=pe(a.maxHeight,o,"clientHeight")}else e=t.clientWidth,i=t.clientHeight}return{width:e,height:i,maxWidth:s||T,maxHeight:n||T}}(t,e,i);let{width:h,height:c}=l;if("content-box"===n.boxSizing){const t=_e(n,"border","width"),e=_e(n,"padding");h-=e.width+t.width,c-=e.height+t.height}h=Math.max(0,h-o.width),c=Math.max(0,s?h/s:c-o.height),h=Me(Math.min(h,a,l.maxWidth)),c=Me(Math.min(c,r,l.maxHeight)),h&&!c&&(c=Me(h/2));return(void 0!==e||void 0!==i)&&s&&l.height&&c>l.height&&(c=l.height,h=Me(Math.floor(c*s))),{width:h,height:c}}function ke(t,e,i){const s=e||1,n=Math.floor(t.height*s),o=Math.floor(t.width*s);t.height=Math.floor(t.height),t.width=Math.floor(t.width);const a=t.canvas;return a.style&&(i||!a.style.height&&!a.style.width)&&(a.style.height=`${t.height}px`,a.style.width=`${t.width}px`),(t.currentDevicePixelRatio!==s||a.height!==n||a.width!==o)&&(t.currentDevicePixelRatio=s,a.height=n,a.width=o,t.ctx.setTransform(s,0,0,s,0,0),!0)}const Se=function(){let t=!1;try{const e={get passive(){return t=!0,!1}};fe()&&(window.addEventListener("test",null,e),window.removeEventListener("test",null,e))}catch(t){}return t}();function Pe(t,e){const i=be(t,e),s=i&&i.match(/^(\d+)(\.\d+)?px$/);return s?+s[1]:void 0}function De(t){return!t||s(t.size)||s(t.family)?null:(t.style?t.style+" ":"")+(t.weight?t.weight+" ":"")+t.size+"px "+t.family}function Ce(t,e,i,s,n){let o=e[n];return o||(o=e[n]=t.measureText(n).width,i.push(n)),o>s&&(s=o),s}function Oe(t,e,i,s){let o=(s=s||{}).data=s.data||{},a=s.garbageCollect=s.garbageCollect||[];s.font!==e&&(o=s.data={},a=s.garbageCollect=[],s.font=e),t.save(),t.font=e;let r=0;const l=i.length;let h,c,d,u,f;for(h=0;hi.length){for(h=0;h0&&t.stroke()}}function Re(t,e,i){return i=i||.5,!e||t&&t.x>e.left-i&&t.xe.top-i&&t.y0&&""!==r.strokeColor;let c,d;for(t.save(),t.font=a.string,function(t,e){e.translation&&t.translate(e.translation[0],e.translation[1]),s(e.rotation)||t.rotate(e.rotation),e.color&&(t.fillStyle=e.color),e.textAlign&&(t.textAlign=e.textAlign),e.textBaseline&&(t.textBaseline=e.textBaseline)}(t,r),c=0;ct[0])){const o=i||t;void 0===s&&(s=ti("_fallback",t));const a={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:t,_rootScopes:o,_fallback:s,_getTarget:n,override:i=>je([i,...t],e,o,s)};return new Proxy(a,{deleteProperty:(e,i)=>(delete e[i],delete e._keys,delete t[0][i],!0),get:(i,s)=>qe(i,s,(()=>function(t,e,i,s){let n;for(const o of e)if(n=ti(Ue(o,t),i),void 0!==n)return Xe(t,n)?Je(i,s,t,n):n}(s,e,t,i))),getOwnPropertyDescriptor:(t,e)=>Reflect.getOwnPropertyDescriptor(t._scopes[0],e),getPrototypeOf:()=>Reflect.getPrototypeOf(t[0]),has:(t,e)=>ei(t).includes(e),ownKeys:t=>ei(t),set(t,e,i){const s=t._storage||(t._storage=n());return t[e]=s[e]=i,delete t._keys,!0}})}function $e(t,e,i,s){const a={_cacheable:!1,_proxy:t,_context:e,_subProxy:i,_stack:new Set,_descriptors:Ye(t,s),setContext:e=>$e(t,e,i,s),override:n=>$e(t.override(n),e,i,s)};return new Proxy(a,{deleteProperty:(e,i)=>(delete e[i],delete t[i],!0),get:(t,e,i)=>qe(t,e,(()=>function(t,e,i){const{_proxy:s,_context:a,_subProxy:r,_descriptors:l}=t;let h=s[e];S(h)&&l.isScriptable(e)&&(h=function(t,e,i,s){const{_proxy:n,_context:o,_subProxy:a,_stack:r}=i;if(r.has(t))throw new Error("Recursion detected: "+Array.from(r).join("->")+"->"+t);r.add(t);let l=e(o,a||s);r.delete(t),Xe(t,l)&&(l=Je(n._scopes,n,t,l));return l}(e,h,t,i));n(h)&&h.length&&(h=function(t,e,i,s){const{_proxy:n,_context:a,_subProxy:r,_descriptors:l}=i;if(void 0!==a.index&&s(t))return e[a.index%e.length];if(o(e[0])){const i=e,s=n._scopes.filter((t=>t!==i));e=[];for(const o of i){const i=Je(s,n,t,o);e.push($e(i,a,r&&r[t],l))}}return e}(e,h,t,l.isIndexable));Xe(e,h)&&(h=$e(h,a,r&&r[e],l));return h}(t,e,i))),getOwnPropertyDescriptor:(e,i)=>e._descriptors.allKeys?Reflect.has(t,i)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(t,i),getPrototypeOf:()=>Reflect.getPrototypeOf(t),has:(e,i)=>Reflect.has(t,i),ownKeys:()=>Reflect.ownKeys(t),set:(e,i,s)=>(t[i]=s,delete e[i],!0)})}function Ye(t,e={scriptable:!0,indexable:!0}){const{_scriptable:i=e.scriptable,_indexable:s=e.indexable,_allKeys:n=e.allKeys}=t;return{allKeys:n,scriptable:i,indexable:s,isScriptable:S(i)?i:()=>i,isIndexable:S(s)?s:()=>s}}const Ue=(t,e)=>t?t+w(e):e,Xe=(t,e)=>o(e)&&"adapters"!==t&&(null===Object.getPrototypeOf(e)||e.constructor===Object);function qe(t,e,i){if(Object.prototype.hasOwnProperty.call(t,e)||"constructor"===e)return t[e];const s=i();return t[e]=s,s}function Ke(t,e,i){return S(t)?t(e,i):t}const Ge=(t,e)=>!0===t?e:"string"==typeof t?M(e,t):void 0;function Ze(t,e,i,s,n){for(const o of e){const e=Ge(i,o);if(e){t.add(e);const o=Ke(e._fallback,i,n);if(void 0!==o&&o!==i&&o!==s)return o}else if(!1===e&&void 0!==s&&i!==s)return null}return!1}function Je(t,e,i,s){const a=e._rootScopes,r=Ke(e._fallback,i,s),l=[...t,...a],h=new Set;h.add(s);let c=Qe(h,l,i,r||i,s);return null!==c&&((void 0===r||r===i||(c=Qe(h,l,r,c,s),null!==c))&&je(Array.from(h),[""],a,r,(()=>function(t,e,i){const s=t._getTarget();e in s||(s[e]={});const a=s[e];if(n(a)&&o(i))return i;return a||{}}(e,i,s))))}function Qe(t,e,i,s,n){for(;i;)i=Ze(t,e,i,s,n);return i}function ti(t,e){for(const i of e){if(!i)continue;const e=i[t];if(void 0!==e)return e}}function ei(t){let e=t._keys;return e||(e=t._keys=function(t){const e=new Set;for(const i of t)for(const t of Object.keys(i).filter((t=>!t.startsWith("_"))))e.add(t);return Array.from(e)}(t._scopes)),e}function ii(t,e,i,s){const{iScale:n}=t,{key:o="r"}=this._parsing,a=new Array(s);let r,l,h,c;for(r=0,l=s;re"x"===t?"y":"x";function ai(t,e,i,s){const n=t.skip?e:t,o=e,a=i.skip?e:i,r=q(o,n),l=q(a,o);let h=r/(r+l),c=l/(r+l);h=isNaN(h)?0:h,c=isNaN(c)?0:c;const d=s*h,u=s*c;return{previous:{x:o.x-d*(a.x-n.x),y:o.y-d*(a.y-n.y)},next:{x:o.x+u*(a.x-n.x),y:o.y+u*(a.y-n.y)}}}function ri(t,e="x"){const i=oi(e),s=t.length,n=Array(s).fill(0),o=Array(s);let a,r,l,h=ni(t,0);for(a=0;a!t.skip))),"monotone"===e.cubicInterpolationMode)ri(t,n);else{let i=s?t[t.length-1]:t[0];for(o=0,a=t.length;o0===t||1===t,di=(t,e,i)=>-Math.pow(2,10*(t-=1))*Math.sin((t-e)*O/i),ui=(t,e,i)=>Math.pow(2,-10*t)*Math.sin((t-e)*O/i)+1,fi={linear:t=>t,easeInQuad:t=>t*t,easeOutQuad:t=>-t*(t-2),easeInOutQuad:t=>(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1),easeInCubic:t=>t*t*t,easeOutCubic:t=>(t-=1)*t*t+1,easeInOutCubic:t=>(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2),easeInQuart:t=>t*t*t*t,easeOutQuart:t=>-((t-=1)*t*t*t-1),easeInOutQuart:t=>(t/=.5)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2),easeInQuint:t=>t*t*t*t*t,easeOutQuint:t=>(t-=1)*t*t*t*t+1,easeInOutQuint:t=>(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2),easeInSine:t=>1-Math.cos(t*E),easeOutSine:t=>Math.sin(t*E),easeInOutSine:t=>-.5*(Math.cos(C*t)-1),easeInExpo:t=>0===t?0:Math.pow(2,10*(t-1)),easeOutExpo:t=>1===t?1:1-Math.pow(2,-10*t),easeInOutExpo:t=>ci(t)?t:t<.5?.5*Math.pow(2,10*(2*t-1)):.5*(2-Math.pow(2,-10*(2*t-1))),easeInCirc:t=>t>=1?t:-(Math.sqrt(1-t*t)-1),easeOutCirc:t=>Math.sqrt(1-(t-=1)*t),easeInOutCirc:t=>(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1),easeInElastic:t=>ci(t)?t:di(t,.075,.3),easeOutElastic:t=>ci(t)?t:ui(t,.075,.3),easeInOutElastic(t){const e=.1125;return ci(t)?t:t<.5?.5*di(2*t,e,.45):.5+.5*ui(2*t-1,e,.45)},easeInBack(t){const e=1.70158;return t*t*((e+1)*t-e)},easeOutBack(t){const e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack(t){let e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:t=>1-fi.easeOutBounce(1-t),easeOutBounce(t){const e=7.5625,i=2.75;return t<1/i?e*t*t:t<2/i?e*(t-=1.5/i)*t+.75:t<2.5/i?e*(t-=2.25/i)*t+.9375:e*(t-=2.625/i)*t+.984375},easeInOutBounce:t=>t<.5?.5*fi.easeInBounce(2*t):.5*fi.easeOutBounce(2*t-1)+.5};function gi(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:t.y+i*(e.y-t.y)}}function pi(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:"middle"===s?i<.5?t.y:e.y:"after"===s?i<1?t.y:e.y:i>0?e.y:t.y}}function mi(t,e,i,s){const n={x:t.cp2x,y:t.cp2y},o={x:e.cp1x,y:e.cp1y},a=gi(t,n,i),r=gi(n,o,i),l=gi(o,e,i),h=gi(a,r,i),c=gi(r,l,i);return gi(h,c,i)}const bi=/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/,xi=/^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/;function _i(t,e){const i=(""+t).match(bi);if(!i||"normal"===i[1])return 1.2*e;switch(t=+i[2],i[3]){case"px":return t;case"%":t/=100}return e*t}const yi=t=>+t||0;function vi(t,e){const i={},s=o(e),n=s?Object.keys(e):e,a=o(t)?s?i=>l(t[i],t[e[i]]):e=>t[e]:()=>t;for(const t of n)i[t]=yi(a(t));return i}function Mi(t){return vi(t,{top:"y",right:"x",bottom:"y",left:"x"})}function wi(t){return vi(t,["topLeft","topRight","bottomLeft","bottomRight"])}function ki(t){const e=Mi(t);return e.width=e.left+e.right,e.height=e.top+e.bottom,e}function Si(t,e){t=t||{},e=e||ue.font;let i=l(t.size,e.size);"string"==typeof i&&(i=parseInt(i,10));let s=l(t.style,e.style);s&&!(""+s).match(xi)&&(console.warn('Invalid font style specified: "'+s+'"'),s=void 0);const n={family:l(t.family,e.family),lineHeight:_i(l(t.lineHeight,e.lineHeight),i),size:i,style:s,weight:l(t.weight,e.weight),string:""};return n.string=De(n),n}function Pi(t,e,i,s){let o,a,r,l=!0;for(o=0,a=t.length;oi&&0===t?0:t+e;return{min:a(s,-Math.abs(o)),max:a(n,o)}}function Ci(t,e){return Object.assign(Object.create(t),e)}function Oi(t,e,i){return t?function(t,e){return{x:i=>t+t+e-i,setWidth(t){e=t},textAlign:t=>"center"===t?t:"right"===t?"left":"right",xPlus:(t,e)=>t-e,leftForLtr:(t,e)=>t-e}}(e,i):{x:t=>t,setWidth(t){},textAlign:t=>t,xPlus:(t,e)=>t+e,leftForLtr:(t,e)=>t}}function Ai(t,e){let i,s;"ltr"!==e&&"rtl"!==e||(i=t.canvas.style,s=[i.getPropertyValue("direction"),i.getPropertyPriority("direction")],i.setProperty("direction",e,"important"),t.prevTextDirection=s)}function Ti(t,e){void 0!==e&&(delete t.prevTextDirection,t.canvas.style.setProperty("direction",e[0],e[1]))}function Li(t){return"angle"===t?{between:Z,compare:K,normalize:G}:{between:tt,compare:(t,e)=>t-e,normalize:t=>t}}function Ei({start:t,end:e,count:i,loop:s,style:n}){return{start:t%i,end:e%i,loop:s&&(e-t+1)%i==0,style:n}}function Ri(t,e,i){if(!i)return[t];const{property:s,start:n,end:o}=i,a=e.length,{compare:r,between:l,normalize:h}=Li(s),{start:c,end:d,loop:u,style:f}=function(t,e,i){const{property:s,start:n,end:o}=i,{between:a,normalize:r}=Li(s),l=e.length;let h,c,{start:d,end:u,loop:f}=t;if(f){for(d+=l,u+=l,h=0,c=l;hx||l(n,b,p)&&0!==r(n,b),v=()=>!x||0===r(o,p)||l(o,b,p);for(let t=c,i=c;t<=d;++t)m=e[t%a],m.skip||(p=h(m[s]),p!==b&&(x=l(p,n,o),null===_&&y()&&(_=0===r(p,n)?t:i),null!==_&&v()&&(g.push(Ei({start:_,end:t,loop:u,count:a,style:f})),_=null),i=t,b=p));return null!==_&&g.push(Ei({start:_,end:d,loop:u,count:a,style:f})),g}function Ii(t,e){const i=[],s=t.segments;for(let n=0;nn&&t[o%e].skip;)o--;return o%=e,{start:n,end:o}}(i,n,o,s);if(!0===s)return Fi(t,[{start:a,end:r,loop:o}],i,e);return Fi(t,function(t,e,i,s){const n=t.length,o=[];let a,r=e,l=t[e];for(a=e+1;a<=i;++a){const i=t[a%n];i.skip||i.stop?l.skip||(s=!1,o.push({start:e%n,end:(a-1)%n,loop:s}),e=r=i.stop?a:null):(r=a,l.skip&&(e=a)),l=i}return null!==r&&o.push({start:e%n,end:r%n,loop:s}),o}(i,a,r{t[a]&&t[a](e[i],n)&&(o.push({element:t,datasetIndex:s,index:l}),r=r||t.inRange(e.x,e.y,n))})),s&&!r?[]:o}var Xi={evaluateInteractionItems:Hi,modes:{index(t,e,i,s){const n=ve(e,t),o=i.axis||"x",a=i.includeInvisible||!1,r=i.intersect?ji(t,n,o,s,a):Yi(t,n,o,!1,s,a),l=[];return r.length?(t.getSortedVisibleDatasetMetas().forEach((t=>{const e=r[0].index,i=t.data[e];i&&!i.skip&&l.push({element:i,datasetIndex:t.index,index:e})})),l):[]},dataset(t,e,i,s){const n=ve(e,t),o=i.axis||"xy",a=i.includeInvisible||!1;let r=i.intersect?ji(t,n,o,s,a):Yi(t,n,o,!1,s,a);if(r.length>0){const e=r[0].datasetIndex,i=t.getDatasetMeta(e).data;r=[];for(let t=0;tji(t,ve(e,t),i.axis||"xy",s,i.includeInvisible||!1),nearest(t,e,i,s){const n=ve(e,t),o=i.axis||"xy",a=i.includeInvisible||!1;return Yi(t,n,o,i.intersect,s,a)},x:(t,e,i,s)=>Ui(t,ve(e,t),"x",i.intersect,s),y:(t,e,i,s)=>Ui(t,ve(e,t),"y",i.intersect,s)}};const qi=["left","top","right","bottom"];function Ki(t,e){return t.filter((t=>t.pos===e))}function Gi(t,e){return t.filter((t=>-1===qi.indexOf(t.pos)&&t.box.axis===e))}function Zi(t,e){return t.sort(((t,i)=>{const s=e?i:t,n=e?t:i;return s.weight===n.weight?s.index-n.index:s.weight-n.weight}))}function Ji(t,e){const i=function(t){const e={};for(const i of t){const{stack:t,pos:s,stackWeight:n}=i;if(!t||!qi.includes(s))continue;const o=e[t]||(e[t]={count:0,placed:0,weight:0,size:0});o.count++,o.weight+=n}return e}(t),{vBoxMaxWidth:s,hBoxMaxHeight:n}=e;let o,a,r;for(o=0,a=t.length;o{s[t]=Math.max(e[t],i[t])})),s}return s(t?["left","right"]:["top","bottom"])}function ss(t,e,i,s){const n=[];let o,a,r,l,h,c;for(o=0,a=t.length,h=0;ot.box.fullSize)),!0),s=Zi(Ki(e,"left"),!0),n=Zi(Ki(e,"right")),o=Zi(Ki(e,"top"),!0),a=Zi(Ki(e,"bottom")),r=Gi(e,"x"),l=Gi(e,"y");return{fullSize:i,leftAndTop:s.concat(o),rightAndBottom:n.concat(l).concat(a).concat(r),chartArea:Ki(e,"chartArea"),vertical:s.concat(n).concat(l),horizontal:o.concat(a).concat(r)}}(t.boxes),l=r.vertical,h=r.horizontal;u(t.boxes,(t=>{"function"==typeof t.beforeLayout&&t.beforeLayout()}));const c=l.reduce(((t,e)=>e.box.options&&!1===e.box.options.display?t:t+1),0)||1,d=Object.freeze({outerWidth:e,outerHeight:i,padding:n,availableWidth:o,availableHeight:a,vBoxMaxWidth:o/2/c,hBoxMaxHeight:a/2}),f=Object.assign({},n);ts(f,ki(s));const g=Object.assign({maxPadding:f,w:o,h:a,x:n.left,y:n.top},n),p=Ji(l.concat(h),d);ss(r.fullSize,g,d,p),ss(l,g,d,p),ss(h,g,d,p)&&ss(l,g,d,p),function(t){const e=t.maxPadding;function i(i){const s=Math.max(e[i]-t[i],0);return t[i]+=s,s}t.y+=i("top"),t.x+=i("left"),i("right"),i("bottom")}(g),os(r.leftAndTop,g,d,p),g.x+=g.w,g.y+=g.h,os(r.rightAndBottom,g,d,p),t.chartArea={left:g.left,top:g.top,right:g.left+g.w,bottom:g.top+g.h,height:g.h,width:g.w},u(r.chartArea,(e=>{const i=e.box;Object.assign(i,t.chartArea),i.update(g.w,g.h,{left:0,top:0,right:0,bottom:0})}))}};class rs{acquireContext(t,e){}releaseContext(t){return!1}addEventListener(t,e,i){}removeEventListener(t,e,i){}getDevicePixelRatio(){return 1}getMaximumSize(t,e,i,s){return e=Math.max(0,e||t.width),i=i||t.height,{width:e,height:Math.max(0,s?Math.floor(e/s):i)}}isAttached(t){return!0}updateConfig(t){}}class ls extends rs{acquireContext(t){return t&&t.getContext&&t.getContext("2d")||null}updateConfig(t){t.options.animation=!1}}const hs="$chartjs",cs={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},ds=t=>null===t||""===t;const us=!!Se&&{passive:!0};function fs(t,e,i){t&&t.canvas&&t.canvas.removeEventListener(e,i,us)}function gs(t,e){for(const i of t)if(i===e||i.contains(e))return!0}function ps(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1;for(const i of t)e=e||gs(i.addedNodes,s),e=e&&!gs(i.removedNodes,s);e&&i()}));return n.observe(document,{childList:!0,subtree:!0}),n}function ms(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1;for(const i of t)e=e||gs(i.removedNodes,s),e=e&&!gs(i.addedNodes,s);e&&i()}));return n.observe(document,{childList:!0,subtree:!0}),n}const bs=new Map;let xs=0;function _s(){const t=window.devicePixelRatio;t!==xs&&(xs=t,bs.forEach(((e,i)=>{i.currentDevicePixelRatio!==t&&e()})))}function ys(t,e,i){const s=t.canvas,n=s&&ge(s);if(!n)return;const o=ct(((t,e)=>{const s=n.clientWidth;i(t,e),s{const e=t[0],i=e.contentRect.width,s=e.contentRect.height;0===i&&0===s||o(i,s)}));return a.observe(n),function(t,e){bs.size||window.addEventListener("resize",_s),bs.set(t,e)}(t,o),a}function vs(t,e,i){i&&i.disconnect(),"resize"===e&&function(t){bs.delete(t),bs.size||window.removeEventListener("resize",_s)}(t)}function Ms(t,e,i){const s=t.canvas,n=ct((e=>{null!==t.ctx&&i(function(t,e){const i=cs[t.type]||t.type,{x:s,y:n}=ve(t,e);return{type:i,chart:e,native:t,x:void 0!==s?s:null,y:void 0!==n?n:null}}(e,t))}),t);return function(t,e,i){t&&t.addEventListener(e,i,us)}(s,e,n),n}class ws extends rs{acquireContext(t,e){const i=t&&t.getContext&&t.getContext("2d");return i&&i.canvas===t?(function(t,e){const i=t.style,s=t.getAttribute("height"),n=t.getAttribute("width");if(t[hs]={initial:{height:s,width:n,style:{display:i.display,height:i.height,width:i.width}}},i.display=i.display||"block",i.boxSizing=i.boxSizing||"border-box",ds(n)){const e=Pe(t,"width");void 0!==e&&(t.width=e)}if(ds(s))if(""===t.style.height)t.height=t.width/(e||2);else{const e=Pe(t,"height");void 0!==e&&(t.height=e)}}(t,e),i):null}releaseContext(t){const e=t.canvas;if(!e[hs])return!1;const i=e[hs].initial;["height","width"].forEach((t=>{const n=i[t];s(n)?e.removeAttribute(t):e.setAttribute(t,n)}));const n=i.style||{};return Object.keys(n).forEach((t=>{e.style[t]=n[t]})),e.width=e.width,delete e[hs],!0}addEventListener(t,e,i){this.removeEventListener(t,e);const s=t.$proxies||(t.$proxies={}),n={attach:ps,detach:ms,resize:ys}[e]||Ms;s[e]=n(t,e,i)}removeEventListener(t,e){const i=t.$proxies||(t.$proxies={}),s=i[e];if(!s)return;({attach:vs,detach:vs,resize:vs}[e]||fs)(t,e,s),i[e]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(t,e,i,s){return we(t,e,i,s)}isAttached(t){const e=t&&ge(t);return!(!e||!e.isConnected)}}function ks(t){return!fe()||"undefined"!=typeof OffscreenCanvas&&t instanceof OffscreenCanvas?ls:ws}var Ss=Object.freeze({__proto__:null,BasePlatform:rs,BasicPlatform:ls,DomPlatform:ws,_detectPlatform:ks});const Ps="transparent",Ds={boolean:(t,e,i)=>i>.5?e:t,color(t,e,i){const s=Qt(t||Ps),n=s.valid&&Qt(e||Ps);return n&&n.valid?n.mix(s,i).hexString():e},number:(t,e,i)=>t+(e-t)*i};class Cs{constructor(t,e,i,s){const n=e[i];s=Pi([t.to,s,n,t.from]);const o=Pi([t.from,n,s]);this._active=!0,this._fn=t.fn||Ds[t.type||typeof o],this._easing=fi[t.easing]||fi.linear,this._start=Math.floor(Date.now()+(t.delay||0)),this._duration=this._total=Math.floor(t.duration),this._loop=!!t.loop,this._target=e,this._prop=i,this._from=o,this._to=s,this._promises=void 0}active(){return this._active}update(t,e,i){if(this._active){this._notify(!1);const s=this._target[this._prop],n=i-this._start,o=this._duration-n;this._start=i,this._duration=Math.floor(Math.max(o,t.duration)),this._total+=n,this._loop=!!t.loop,this._to=Pi([t.to,e,s,t.from]),this._from=Pi([t.from,s,e])}}cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._notify(!1))}tick(t){const e=t-this._start,i=this._duration,s=this._prop,n=this._from,o=this._loop,a=this._to;let r;if(this._active=n!==a&&(o||e1?2-r:r,r=this._easing(Math.min(1,Math.max(0,r))),this._target[s]=this._fn(n,a,r))}wait(){const t=this._promises||(this._promises=[]);return new Promise(((e,i)=>{t.push({res:e,rej:i})}))}_notify(t){const e=t?"res":"rej",i=this._promises||[];for(let t=0;t{const a=t[s];if(!o(a))return;const r={};for(const t of e)r[t]=a[t];(n(a.properties)&&a.properties||[s]).forEach((t=>{t!==s&&i.has(t)||i.set(t,r)}))}))}_animateOptions(t,e){const i=e.options,s=function(t,e){if(!e)return;let i=t.options;if(!i)return void(t.options=e);i.$shared&&(t.options=i=Object.assign({},i,{$shared:!1,$animations:{}}));return i}(t,i);if(!s)return[];const n=this._createAnimations(s,i);return i.$shared&&function(t,e){const i=[],s=Object.keys(e);for(let e=0;e{t.options=i}),(()=>{})),n}_createAnimations(t,e){const i=this._properties,s=[],n=t.$animations||(t.$animations={}),o=Object.keys(e),a=Date.now();let r;for(r=o.length-1;r>=0;--r){const l=o[r];if("$"===l.charAt(0))continue;if("options"===l){s.push(...this._animateOptions(t,e));continue}const h=e[l];let c=n[l];const d=i.get(l);if(c){if(d&&c.active()){c.update(d,h,a);continue}c.cancel()}d&&d.duration?(n[l]=c=new Cs(d,t,l,h),s.push(c)):t[l]=h}return s}update(t,e){if(0===this._properties.size)return void Object.assign(t,e);const i=this._createAnimations(t,e);return i.length?(xt.add(this._chart,i),!0):void 0}}function As(t,e){const i=t&&t.options||{},s=i.reverse,n=void 0===i.min?e:0,o=void 0===i.max?e:0;return{start:s?o:n,end:s?n:o}}function Ts(t,e){const i=[],s=t._getSortedDatasetMetas(e);let n,o;for(n=0,o=s.length;n0||!i&&e<0)return n.index}return null}function zs(t,e){const{chart:i,_cachedMeta:s}=t,n=i._stacks||(i._stacks={}),{iScale:o,vScale:a,index:r}=s,l=o.axis,h=a.axis,c=function(t,e,i){return`${t.id}.${e.id}.${i.stack||i.type}`}(o,a,s),d=e.length;let u;for(let t=0;ti[t].axis===e)).shift()}function Vs(t,e){const i=t.controller.index,s=t.vScale&&t.vScale.axis;if(s){e=e||t._parsed;for(const t of e){const e=t._stacks;if(!e||void 0===e[s]||void 0===e[s][i])return;delete e[s][i],void 0!==e[s]._visualValues&&void 0!==e[s]._visualValues[i]&&delete e[s]._visualValues[i]}}}const Bs=t=>"reset"===t||"none"===t,Ws=(t,e)=>e?t:Object.assign({},t);class Ns{static defaults={};static datasetElementType=null;static dataElementType=null;constructor(t,e){this.chart=t,this._ctx=t.ctx,this.index=e,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.supportsDecimation=!1,this.$context=void 0,this._syncList=[],this.datasetElementType=new.target.datasetElementType,this.dataElementType=new.target.dataElementType,this.initialize()}initialize(){const t=this._cachedMeta;this.configure(),this.linkScales(),t._stacked=Es(t.vScale,t),this.addElements(),this.options.fill&&!this.chart.isPluginEnabled("filler")&&console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options")}updateIndex(t){this.index!==t&&Vs(this._cachedMeta),this.index=t}linkScales(){const t=this.chart,e=this._cachedMeta,i=this.getDataset(),s=(t,e,i,s)=>"x"===t?e:"r"===t?s:i,n=e.xAxisID=l(i.xAxisID,Fs(t,"x")),o=e.yAxisID=l(i.yAxisID,Fs(t,"y")),a=e.rAxisID=l(i.rAxisID,Fs(t,"r")),r=e.indexAxis,h=e.iAxisID=s(r,n,o,a),c=e.vAxisID=s(r,o,n,a);e.xScale=this.getScaleForId(n),e.yScale=this.getScaleForId(o),e.rScale=this.getScaleForId(a),e.iScale=this.getScaleForId(h),e.vScale=this.getScaleForId(c)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(t){return this.chart.scales[t]}_getOtherScale(t){const e=this._cachedMeta;return t===e.iScale?e.vScale:e.iScale}reset(){this._update("reset")}_destroy(){const t=this._cachedMeta;this._data&&rt(this._data,this),t._stacked&&Vs(t)}_dataCheck(){const t=this.getDataset(),e=t.data||(t.data=[]),i=this._data;if(o(e)){const t=this._cachedMeta;this._data=function(t,e){const{iScale:i,vScale:s}=e,n="x"===i.axis?"x":"y",o="x"===s.axis?"x":"y",a=Object.keys(t),r=new Array(a.length);let l,h,c;for(l=0,h=a.length;l0&&i._parsed[t-1];if(!1===this._parsing)i._parsed=s,i._sorted=!0,d=s;else{d=n(s[t])?this.parseArrayData(i,s,t,e):o(s[t])?this.parseObjectData(i,s,t,e):this.parsePrimitiveData(i,s,t,e);const a=()=>null===c[l]||f&&c[l]t&&!e.hidden&&e._stacked&&{keys:Ts(i,!0),values:null})(e,i,this.chart),h={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY},{min:c,max:d}=function(t){const{min:e,max:i,minDefined:s,maxDefined:n}=t.getUserBounds();return{min:s?e:Number.NEGATIVE_INFINITY,max:n?i:Number.POSITIVE_INFINITY}}(r);let u,f;function g(){f=s[u];const e=f[r.axis];return!a(f[t.axis])||c>e||d=0;--u)if(!g()){this.updateRangeFromParsed(h,t,f,l);break}return h}getAllParsedValues(t){const e=this._cachedMeta._parsed,i=[];let s,n,o;for(s=0,n=e.length;s=0&&tthis.getContext(i,s,e)),c);return f.$shared&&(f.$shared=r,n[o]=Object.freeze(Ws(f,r))),f}_resolveAnimations(t,e,i){const s=this.chart,n=this._cachedDataOpts,o=`animation-${e}`,a=n[o];if(a)return a;let r;if(!1!==s.options.animation){const s=this.chart.config,n=s.datasetAnimationScopeKeys(this._type,e),o=s.getOptionScopes(this.getDataset(),n);r=s.createResolver(o,this.getContext(t,i,e))}const l=new Os(s,r&&r.animations);return r&&r._cacheable&&(n[o]=Object.freeze(l)),l}getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},t))}includeOptions(t,e){return!e||Bs(t)||this.chart._animationsDisabled}_getSharedOptions(t,e){const i=this.resolveDataElementOptions(t,e),s=this._sharedOptions,n=this.getSharedOptions(i),o=this.includeOptions(e,n)||n!==s;return this.updateSharedOptions(n,e,i),{sharedOptions:n,includeOptions:o}}updateElement(t,e,i,s){Bs(s)?Object.assign(t,i):this._resolveAnimations(e,s).update(t,i)}updateSharedOptions(t,e,i){t&&!Bs(e)&&this._resolveAnimations(void 0,e).update(t,i)}_setStyle(t,e,i,s){t.active=s;const n=this.getStyle(e,s);this._resolveAnimations(e,i,s).update(t,{options:!s&&this.getSharedOptions(n)||n})}removeHoverStyle(t,e,i){this._setStyle(t,i,"active",!1)}setHoverStyle(t,e,i){this._setStyle(t,i,"active",!0)}_removeDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!1)}_setDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!0)}_resyncElements(t){const e=this._data,i=this._cachedMeta.data;for(const[t,e,i]of this._syncList)this[t](e,i);this._syncList=[];const s=i.length,n=e.length,o=Math.min(n,s);o&&this.parse(0,o),n>s?this._insertElements(s,n-s,t):n{for(t.length+=e,a=t.length-1;a>=o;a--)t[a]=t[a-e]};for(r(n),a=t;a{s[t]=i[t]&&i[t].active()?i[t]._to:this[t]})),s}}function js(t,e){const i=t.options.ticks,n=function(t){const e=t.options.offset,i=t._tickSize(),s=t._length/i+(e?0:1),n=t._maxLength/i;return Math.floor(Math.min(s,n))}(t),o=Math.min(i.maxTicksLimit||n,n),a=i.major.enabled?function(t){const e=[];let i,s;for(i=0,s=t.length;io)return function(t,e,i,s){let n,o=0,a=i[0];for(s=Math.ceil(s),n=0;nn)return e}return Math.max(n,1)}(a,e,o);if(r>0){let t,i;const n=r>1?Math.round((h-l)/(r-1)):null;for($s(e,c,d,s(n)?0:l-n,l),t=0,i=r-1;t"top"===e||"left"===e?t[e]+i:t[e]-i,Us=(t,e)=>Math.min(e||t,t);function Xs(t,e){const i=[],s=t.length/e,n=t.length;let o=0;for(;oa+r)))return h}function Ks(t){return t.drawTicks?t.tickLength:0}function Gs(t,e){if(!t.display)return 0;const i=Si(t.font,e),s=ki(t.padding);return(n(t.text)?t.text.length:1)*i.lineHeight+s.height}function Zs(t,e,i){let s=ut(t);return(i&&"right"!==e||!i&&"right"===e)&&(s=(t=>"left"===t?"right":"right"===t?"left":t)(s)),s}class Js extends Hs{constructor(t){super(),this.id=t.id,this.type=t.type,this.options=void 0,this.ctx=t.ctx,this.chart=t.chart,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this._margins={left:0,right:0,top:0,bottom:0},this.maxWidth=void 0,this.maxHeight=void 0,this.paddingTop=void 0,this.paddingBottom=void 0,this.paddingLeft=void 0,this.paddingRight=void 0,this.axis=void 0,this.labelRotation=void 0,this.min=void 0,this.max=void 0,this._range=void 0,this.ticks=[],this._gridLineItems=null,this._labelItems=null,this._labelSizes=null,this._length=0,this._maxLength=0,this._longestTextCache={},this._startPixel=void 0,this._endPixel=void 0,this._reversePixels=!1,this._userMax=void 0,this._userMin=void 0,this._suggestedMax=void 0,this._suggestedMin=void 0,this._ticksLength=0,this._borderValue=0,this._cache={},this._dataLimitsCached=!1,this.$context=void 0}init(t){this.options=t.setContext(this.getContext()),this.axis=t.axis,this._userMin=this.parse(t.min),this._userMax=this.parse(t.max),this._suggestedMin=this.parse(t.suggestedMin),this._suggestedMax=this.parse(t.suggestedMax)}parse(t,e){return t}getUserBounds(){let{_userMin:t,_userMax:e,_suggestedMin:i,_suggestedMax:s}=this;return t=r(t,Number.POSITIVE_INFINITY),e=r(e,Number.NEGATIVE_INFINITY),i=r(i,Number.POSITIVE_INFINITY),s=r(s,Number.NEGATIVE_INFINITY),{min:r(t,i),max:r(e,s),minDefined:a(t),maxDefined:a(e)}}getMinMax(t){let e,{min:i,max:s,minDefined:n,maxDefined:o}=this.getUserBounds();if(n&&o)return{min:i,max:s};const a=this.getMatchingVisibleMetas();for(let r=0,l=a.length;rs?s:i,s=n&&i>s?i:s,{min:r(i,r(s,i)),max:r(s,r(i,s))}}getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){const t=this.chart.data;return this.options.labels||(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels||[]}getLabelItems(t=this.chart.chartArea){return this._labelItems||(this._labelItems=this._computeLabelItems(t))}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){d(this.options.beforeUpdate,[this])}update(t,e,i){const{beginAtZero:s,grace:n,ticks:o}=this.options,a=o.sampleSize;this.beforeUpdate(),this.maxWidth=t,this.maxHeight=e,this._margins=i=Object.assign({left:0,right:0,top:0,bottom:0},i),this.ticks=null,this._labelSizes=null,this._gridLineItems=null,this._labelItems=null,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this._maxLength=this.isHorizontal()?this.width+i.left+i.right:this.height+i.top+i.bottom,this._dataLimitsCached||(this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this._range=Di(this,n,s),this._dataLimitsCached=!0),this.beforeBuildTicks(),this.ticks=this.buildTicks()||[],this.afterBuildTicks();const r=a=n||i<=1||!this.isHorizontal())return void(this.labelRotation=s);const h=this._getLabelSizes(),c=h.widest.width,d=h.highest.height,u=J(this.chart.width-c,0,this.maxWidth);o=t.offset?this.maxWidth/i:u/(i-1),c+6>o&&(o=u/(i-(t.offset?.5:1)),a=this.maxHeight-Ks(t.grid)-e.padding-Gs(t.title,this.chart.options.font),r=Math.sqrt(c*c+d*d),l=Y(Math.min(Math.asin(J((h.highest.height+6)/o,-1,1)),Math.asin(J(a/r,-1,1))-Math.asin(J(d/r,-1,1)))),l=Math.max(s,Math.min(n,l))),this.labelRotation=l}afterCalculateLabelRotation(){d(this.options.afterCalculateLabelRotation,[this])}afterAutoSkip(){}beforeFit(){d(this.options.beforeFit,[this])}fit(){const t={width:0,height:0},{chart:e,options:{ticks:i,title:s,grid:n}}=this,o=this._isVisible(),a=this.isHorizontal();if(o){const o=Gs(s,e.options.font);if(a?(t.width=this.maxWidth,t.height=Ks(n)+o):(t.height=this.maxHeight,t.width=Ks(n)+o),i.display&&this.ticks.length){const{first:e,last:s,widest:n,highest:o}=this._getLabelSizes(),r=2*i.padding,l=$(this.labelRotation),h=Math.cos(l),c=Math.sin(l);if(a){const e=i.mirror?0:c*n.width+h*o.height;t.height=Math.min(this.maxHeight,t.height+e+r)}else{const e=i.mirror?0:h*n.width+c*o.height;t.width=Math.min(this.maxWidth,t.width+e+r)}this._calculatePadding(e,s,c,h)}}this._handleMargins(),a?(this.width=this._length=e.width-this._margins.left-this._margins.right,this.height=t.height):(this.width=t.width,this.height=this._length=e.height-this._margins.top-this._margins.bottom)}_calculatePadding(t,e,i,s){const{ticks:{align:n,padding:o},position:a}=this.options,r=0!==this.labelRotation,l="top"!==a&&"x"===this.axis;if(this.isHorizontal()){const a=this.getPixelForTick(0)-this.left,h=this.right-this.getPixelForTick(this.ticks.length-1);let c=0,d=0;r?l?(c=s*t.width,d=i*e.height):(c=i*t.height,d=s*e.width):"start"===n?d=e.width:"end"===n?c=t.width:"inner"!==n&&(c=t.width/2,d=e.width/2),this.paddingLeft=Math.max((c-a+o)*this.width/(this.width-a),0),this.paddingRight=Math.max((d-h+o)*this.width/(this.width-h),0)}else{let i=e.height/2,s=t.height/2;"start"===n?(i=0,s=t.height):"end"===n&&(i=e.height,s=0),this.paddingTop=i+o,this.paddingBottom=s+o}}_handleMargins(){this._margins&&(this._margins.left=Math.max(this.paddingLeft,this._margins.left),this._margins.top=Math.max(this.paddingTop,this._margins.top),this._margins.right=Math.max(this.paddingRight,this._margins.right),this._margins.bottom=Math.max(this.paddingBottom,this._margins.bottom))}afterFit(){d(this.options.afterFit,[this])}isHorizontal(){const{axis:t,position:e}=this.options;return"top"===e||"bottom"===e||"x"===t}isFullSize(){return this.options.fullSize}_convertTicksToLabels(t){let e,i;for(this.beforeTickToLabelConversion(),this.generateTickLabels(t),e=0,i=t.length;e{const i=t.gc,s=i.length/2;let n;if(s>e){for(n=0;n({width:r[t]||0,height:l[t]||0});return{first:P(0),last:P(e-1),widest:P(k),highest:P(S),widths:r,heights:l}}getLabelForValue(t){return t}getPixelForValue(t,e){return NaN}getValueForPixel(t){}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getPixelForDecimal(t){this._reversePixels&&(t=1-t);const e=this._startPixel+t*this._length;return Q(this._alignToPixels?Ae(this.chart,e,0):e)}getDecimalForPixel(t){const e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){const{min:t,max:e}=this;return t<0&&e<0?e:t>0&&e>0?t:0}getContext(t){const e=this.ticks||[];if(t>=0&&ta*s?a/i:r/s:r*s0}_computeGridLineItems(t){const e=this.axis,i=this.chart,s=this.options,{grid:n,position:a,border:r}=s,h=n.offset,c=this.isHorizontal(),d=this.ticks.length+(h?1:0),u=Ks(n),f=[],g=r.setContext(this.getContext()),p=g.display?g.width:0,m=p/2,b=function(t){return Ae(i,t,p)};let x,_,y,v,M,w,k,S,P,D,C,O;if("top"===a)x=b(this.bottom),w=this.bottom-u,S=x-m,D=b(t.top)+m,O=t.bottom;else if("bottom"===a)x=b(this.top),D=t.top,O=b(t.bottom)-m,w=x+m,S=this.top+u;else if("left"===a)x=b(this.right),M=this.right-u,k=x-m,P=b(t.left)+m,C=t.right;else if("right"===a)x=b(this.left),P=t.left,C=b(t.right)-m,M=x+m,k=this.left+u;else if("x"===e){if("center"===a)x=b((t.top+t.bottom)/2+.5);else if(o(a)){const t=Object.keys(a)[0],e=a[t];x=b(this.chart.scales[t].getPixelForValue(e))}D=t.top,O=t.bottom,w=x+m,S=w+u}else if("y"===e){if("center"===a)x=b((t.left+t.right)/2);else if(o(a)){const t=Object.keys(a)[0],e=a[t];x=b(this.chart.scales[t].getPixelForValue(e))}M=x-m,k=M-u,P=t.left,C=t.right}const A=l(s.ticks.maxTicksLimit,d),T=Math.max(1,Math.ceil(d/A));for(_=0;_0&&(o-=s/2)}d={left:o,top:n,width:s+e.width,height:i+e.height,color:t.backdropColor}}b.push({label:v,font:P,textOffset:O,options:{rotation:m,color:i,strokeColor:o,strokeWidth:h,textAlign:f,textBaseline:A,translation:[M,w],backdrop:d}})}return b}_getXAxisLabelAlignment(){const{position:t,ticks:e}=this.options;if(-$(this.labelRotation))return"top"===t?"left":"right";let i="center";return"start"===e.align?i="left":"end"===e.align?i="right":"inner"===e.align&&(i="inner"),i}_getYAxisLabelAlignment(t){const{position:e,ticks:{crossAlign:i,mirror:s,padding:n}}=this.options,o=t+n,a=this._getLabelSizes().widest.width;let r,l;return"left"===e?s?(l=this.right+n,"near"===i?r="left":"center"===i?(r="center",l+=a/2):(r="right",l+=a)):(l=this.right-o,"near"===i?r="right":"center"===i?(r="center",l-=a/2):(r="left",l=this.left)):"right"===e?s?(l=this.left+n,"near"===i?r="right":"center"===i?(r="center",l-=a/2):(r="left",l-=a)):(l=this.left+o,"near"===i?r="left":"center"===i?(r="center",l+=a/2):(r="right",l=this.right)):r="right",{textAlign:r,x:l}}_computeLabelArea(){if(this.options.ticks.mirror)return;const t=this.chart,e=this.options.position;return"left"===e||"right"===e?{top:0,left:this.left,bottom:t.height,right:this.right}:"top"===e||"bottom"===e?{top:this.top,left:0,bottom:this.bottom,right:t.width}:void 0}drawBackground(){const{ctx:t,options:{backgroundColor:e},left:i,top:s,width:n,height:o}=this;e&&(t.save(),t.fillStyle=e,t.fillRect(i,s,n,o),t.restore())}getLineWidthForValue(t){const e=this.options.grid;if(!this._isVisible()||!e.display)return 0;const i=this.ticks.findIndex((e=>e.value===t));if(i>=0){return e.setContext(this.getContext(i)).lineWidth}return 0}drawGrid(t){const e=this.options.grid,i=this.ctx,s=this._gridLineItems||(this._gridLineItems=this._computeGridLineItems(t));let n,o;const a=(t,e,s)=>{s.width&&s.color&&(i.save(),i.lineWidth=s.width,i.strokeStyle=s.color,i.setLineDash(s.borderDash||[]),i.lineDashOffset=s.borderDashOffset,i.beginPath(),i.moveTo(t.x,t.y),i.lineTo(e.x,e.y),i.stroke(),i.restore())};if(e.display)for(n=0,o=s.length;n{this.drawBackground(),this.drawGrid(t),this.drawTitle()}},{z:s,draw:()=>{this.drawBorder()}},{z:e,draw:t=>{this.drawLabels(t)}}]:[{z:e,draw:t=>{this.draw(t)}}]}getMatchingVisibleMetas(t){const e=this.chart.getSortedVisibleDatasetMetas(),i=this.axis+"AxisID",s=[];let n,o;for(n=0,o=e.length;n{const s=i.split("."),n=s.pop(),o=[t].concat(s).join("."),a=e[i].split("."),r=a.pop(),l=a.join(".");ue.route(o,n,l,r)}))}(e,t.defaultRoutes);t.descriptors&&ue.describe(e,t.descriptors)}(t,o,i),this.override&&ue.override(t.id,t.overrides)),o}get(t){return this.items[t]}unregister(t){const e=this.items,i=t.id,s=this.scope;i in e&&delete e[i],s&&i in ue[s]&&(delete ue[s][i],this.override&&delete re[i])}}class tn{constructor(){this.controllers=new Qs(Ns,"datasets",!0),this.elements=new Qs(Hs,"elements"),this.plugins=new Qs(Object,"plugins"),this.scales=new Qs(Js,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...t){this._each("register",t)}remove(...t){this._each("unregister",t)}addControllers(...t){this._each("register",t,this.controllers)}addElements(...t){this._each("register",t,this.elements)}addPlugins(...t){this._each("register",t,this.plugins)}addScales(...t){this._each("register",t,this.scales)}getController(t){return this._get(t,this.controllers,"controller")}getElement(t){return this._get(t,this.elements,"element")}getPlugin(t){return this._get(t,this.plugins,"plugin")}getScale(t){return this._get(t,this.scales,"scale")}removeControllers(...t){this._each("unregister",t,this.controllers)}removeElements(...t){this._each("unregister",t,this.elements)}removePlugins(...t){this._each("unregister",t,this.plugins)}removeScales(...t){this._each("unregister",t,this.scales)}_each(t,e,i){[...e].forEach((e=>{const s=i||this._getRegistryForType(e);i||s.isForType(e)||s===this.plugins&&e.id?this._exec(t,s,e):u(e,(e=>{const s=i||this._getRegistryForType(e);this._exec(t,s,e)}))}))}_exec(t,e,i){const s=w(t);d(i["before"+s],[],i),e[t](i),d(i["after"+s],[],i)}_getRegistryForType(t){for(let e=0;et.filter((t=>!e.some((e=>t.plugin.id===e.plugin.id))));this._notify(s(e,i),t,"stop"),this._notify(s(i,e),t,"start")}}function nn(t,e){return e||!1!==t?!0===t?{}:t:null}function on(t,{plugin:e,local:i},s,n){const o=t.pluginScopeKeys(e),a=t.getOptionScopes(s,o);return i&&e.defaults&&a.push(e.defaults),t.createResolver(a,n,[""],{scriptable:!1,indexable:!1,allKeys:!0})}function an(t,e){const i=ue.datasets[t]||{};return((e.datasets||{})[t]||{}).indexAxis||e.indexAxis||i.indexAxis||"x"}function rn(t){if("x"===t||"y"===t||"r"===t)return t}function ln(t,...e){if(rn(t))return t;for(const s of e){const e=s.axis||("top"===(i=s.position)||"bottom"===i?"x":"left"===i||"right"===i?"y":void 0)||t.length>1&&rn(t[0].toLowerCase());if(e)return e}var i;throw new Error(`Cannot determine type of '${t}' axis. Please provide 'axis' or 'position' option.`)}function hn(t,e,i){if(i[e+"AxisID"]===t)return{axis:e}}function cn(t,e){const i=re[t.type]||{scales:{}},s=e.scales||{},n=an(t.type,e),a=Object.create(null);return Object.keys(s).forEach((e=>{const r=s[e];if(!o(r))return console.error(`Invalid scale configuration for scale: ${e}`);if(r._proxy)return console.warn(`Ignoring resolver passed as options for scale: ${e}`);const l=ln(e,r,function(t,e){if(e.data&&e.data.datasets){const i=e.data.datasets.filter((e=>e.xAxisID===t||e.yAxisID===t));if(i.length)return hn(t,"x",i[0])||hn(t,"y",i[0])}return{}}(e,t),ue.scales[r.type]),h=function(t,e){return t===e?"_index_":"_value_"}(l,n),c=i.scales||{};a[e]=x(Object.create(null),[{axis:l},r,c[l],c[h]])})),t.data.datasets.forEach((i=>{const n=i.type||t.type,o=i.indexAxis||an(n,e),r=(re[n]||{}).scales||{};Object.keys(r).forEach((t=>{const e=function(t,e){let i=t;return"_index_"===t?i=e:"_value_"===t&&(i="x"===e?"y":"x"),i}(t,o),n=i[e+"AxisID"]||e;a[n]=a[n]||Object.create(null),x(a[n],[{axis:e},s[n],r[t]])}))})),Object.keys(a).forEach((t=>{const e=a[t];x(e,[ue.scales[e.type],ue.scale])})),a}function dn(t){const e=t.options||(t.options={});e.plugins=l(e.plugins,{}),e.scales=cn(t,e)}function un(t){return(t=t||{}).datasets=t.datasets||[],t.labels=t.labels||[],t}const fn=new Map,gn=new Set;function pn(t,e){let i=fn.get(t);return i||(i=e(),fn.set(t,i),gn.add(i)),i}const mn=(t,e,i)=>{const s=M(e,i);void 0!==s&&t.add(s)};class bn{constructor(t){this._config=function(t){return(t=t||{}).data=un(t.data),dn(t),t}(t),this._scopeCache=new Map,this._resolverCache=new Map}get platform(){return this._config.platform}get type(){return this._config.type}set type(t){this._config.type=t}get data(){return this._config.data}set data(t){this._config.data=un(t)}get options(){return this._config.options}set options(t){this._config.options=t}get plugins(){return this._config.plugins}update(){const t=this._config;this.clearCache(),dn(t)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(t){return pn(t,(()=>[[`datasets.${t}`,""]]))}datasetAnimationScopeKeys(t,e){return pn(`${t}.transition.${e}`,(()=>[[`datasets.${t}.transitions.${e}`,`transitions.${e}`],[`datasets.${t}`,""]]))}datasetElementScopeKeys(t,e){return pn(`${t}-${e}`,(()=>[[`datasets.${t}.elements.${e}`,`datasets.${t}`,`elements.${e}`,""]]))}pluginScopeKeys(t){const e=t.id;return pn(`${this.type}-plugin-${e}`,(()=>[[`plugins.${e}`,...t.additionalOptionScopes||[]]]))}_cachedScopes(t,e){const i=this._scopeCache;let s=i.get(t);return s&&!e||(s=new Map,i.set(t,s)),s}getOptionScopes(t,e,i){const{options:s,type:n}=this,o=this._cachedScopes(t,i),a=o.get(e);if(a)return a;const r=new Set;e.forEach((e=>{t&&(r.add(t),e.forEach((e=>mn(r,t,e)))),e.forEach((t=>mn(r,s,t))),e.forEach((t=>mn(r,re[n]||{},t))),e.forEach((t=>mn(r,ue,t))),e.forEach((t=>mn(r,le,t)))}));const l=Array.from(r);return 0===l.length&&l.push(Object.create(null)),gn.has(e)&&o.set(e,l),l}chartOptionScopes(){const{options:t,type:e}=this;return[t,re[e]||{},ue.datasets[e]||{},{type:e},ue,le]}resolveNamedOptions(t,e,i,s=[""]){const o={$shared:!0},{resolver:a,subPrefixes:r}=xn(this._resolverCache,t,s);let l=a;if(function(t,e){const{isScriptable:i,isIndexable:s}=Ye(t);for(const o of e){const e=i(o),a=s(o),r=(a||e)&&t[o];if(e&&(S(r)||_n(r))||a&&n(r))return!0}return!1}(a,e)){o.$shared=!1;l=$e(a,i=S(i)?i():i,this.createResolver(t,i,r))}for(const t of e)o[t]=l[t];return o}createResolver(t,e,i=[""],s){const{resolver:n}=xn(this._resolverCache,t,i);return o(e)?$e(n,e,void 0,s):n}}function xn(t,e,i){let s=t.get(e);s||(s=new Map,t.set(e,s));const n=i.join();let o=s.get(n);if(!o){o={resolver:je(e,i),subPrefixes:i.filter((t=>!t.toLowerCase().includes("hover")))},s.set(n,o)}return o}const _n=t=>o(t)&&Object.getOwnPropertyNames(t).some((e=>S(t[e])));const yn=["top","bottom","left","right","chartArea"];function vn(t,e){return"top"===t||"bottom"===t||-1===yn.indexOf(t)&&"x"===e}function Mn(t,e){return function(i,s){return i[t]===s[t]?i[e]-s[e]:i[t]-s[t]}}function wn(t){const e=t.chart,i=e.options.animation;e.notifyPlugins("afterRender"),d(i&&i.onComplete,[t],e)}function kn(t){const e=t.chart,i=e.options.animation;d(i&&i.onProgress,[t],e)}function Sn(t){return fe()&&"string"==typeof t?t=document.getElementById(t):t&&t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas),t}const Pn={},Dn=t=>{const e=Sn(t);return Object.values(Pn).filter((t=>t.canvas===e)).pop()};function Cn(t,e,i){const s=Object.keys(t);for(const n of s){const s=+n;if(s>=e){const o=t[n];delete t[n],(i>0||s>e)&&(t[s+i]=o)}}}function On(t,e,i){return t.options.clip?t[i]:e[i]}class An{static defaults=ue;static instances=Pn;static overrides=re;static registry=en;static version="4.4.6";static getChart=Dn;static register(...t){en.add(...t),Tn()}static unregister(...t){en.remove(...t),Tn()}constructor(t,e){const s=this.config=new bn(e),n=Sn(t),o=Dn(n);if(o)throw new Error("Canvas is already in use. Chart with ID '"+o.id+"' must be destroyed before the canvas with ID '"+o.canvas.id+"' can be reused.");const a=s.createResolver(s.chartOptionScopes(),this.getContext());this.platform=new(s.platform||ks(n)),this.platform.updateConfig(s);const r=this.platform.acquireContext(n,a.aspectRatio),l=r&&r.canvas,h=l&&l.height,c=l&&l.width;this.id=i(),this.ctx=r,this.canvas=l,this.width=c,this.height=h,this._options=a,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this._plugins=new sn,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=dt((t=>this.update(t)),a.resizeDelay||0),this._dataChanges=[],Pn[this.id]=this,r&&l?(xt.listen(this,"complete",wn),xt.listen(this,"progress",kn),this._initialize(),this.attached&&this.update()):console.error("Failed to create chart: can't acquire context from the given item")}get aspectRatio(){const{options:{aspectRatio:t,maintainAspectRatio:e},width:i,height:n,_aspectRatio:o}=this;return s(t)?e&&o?o:n?i/n:null:t}get data(){return this.config.data}set data(t){this.config.data=t}get options(){return this._options}set options(t){this.config.options=t}get registry(){return en}_initialize(){return this.notifyPlugins("beforeInit"),this.options.responsive?this.resize():ke(this,this.options.devicePixelRatio),this.bindEvents(),this.notifyPlugins("afterInit"),this}clear(){return Te(this.canvas,this.ctx),this}stop(){return xt.stop(this),this}resize(t,e){xt.running(this)?this._resizeBeforeDraw={width:t,height:e}:this._resize(t,e)}_resize(t,e){const i=this.options,s=this.canvas,n=i.maintainAspectRatio&&this.aspectRatio,o=this.platform.getMaximumSize(s,t,e,n),a=i.devicePixelRatio||this.platform.getDevicePixelRatio(),r=this.width?"resize":"attach";this.width=o.width,this.height=o.height,this._aspectRatio=this.aspectRatio,ke(this,a,!0)&&(this.notifyPlugins("resize",{size:o}),d(i.onResize,[this,o],this),this.attached&&this._doResize(r)&&this.render())}ensureScalesHaveIDs(){u(this.options.scales||{},((t,e)=>{t.id=e}))}buildOrUpdateScales(){const t=this.options,e=t.scales,i=this.scales,s=Object.keys(i).reduce(((t,e)=>(t[e]=!1,t)),{});let n=[];e&&(n=n.concat(Object.keys(e).map((t=>{const i=e[t],s=ln(t,i),n="r"===s,o="x"===s;return{options:i,dposition:n?"chartArea":o?"bottom":"left",dtype:n?"radialLinear":o?"category":"linear"}})))),u(n,(e=>{const n=e.options,o=n.id,a=ln(o,n),r=l(n.type,e.dtype);void 0!==n.position&&vn(n.position,a)===vn(e.dposition)||(n.position=e.dposition),s[o]=!0;let h=null;if(o in i&&i[o].type===r)h=i[o];else{h=new(en.getScale(r))({id:o,type:r,ctx:this.ctx,chart:this}),i[h.id]=h}h.init(n,t)})),u(s,((t,e)=>{t||delete i[e]})),u(i,(t=>{as.configure(this,t,t.options),as.addBox(this,t)}))}_updateMetasets(){const t=this._metasets,e=this.data.datasets.length,i=t.length;if(t.sort(((t,e)=>t.index-e.index)),i>e){for(let t=e;te.length&&delete this._stacks,t.forEach(((t,i)=>{0===e.filter((e=>e===t._dataset)).length&&this._destroyDatasetMeta(i)}))}buildOrUpdateControllers(){const t=[],e=this.data.datasets;let i,s;for(this._removeUnreferencedMetasets(),i=0,s=e.length;i{this.getDatasetMeta(e).controller.reset()}),this)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(t){const e=this.config;e.update();const i=this._options=e.createResolver(e.chartOptionScopes(),this.getContext()),s=this._animationsDisabled=!i.animation;if(this._updateScales(),this._checkEventBindings(),this._updateHiddenIndices(),this._plugins.invalidate(),!1===this.notifyPlugins("beforeUpdate",{mode:t,cancelable:!0}))return;const n=this.buildOrUpdateControllers();this.notifyPlugins("beforeElementsUpdate");let o=0;for(let t=0,e=this.data.datasets.length;t{t.reset()})),this._updateDatasets(t),this.notifyPlugins("afterUpdate",{mode:t}),this._layers.sort(Mn("z","_idx"));const{_active:a,_lastEvent:r}=this;r?this._eventHandler(r,!0):a.length&&this._updateHoverStyles(a,a,!0),this.render()}_updateScales(){u(this.scales,(t=>{as.removeBox(this,t)})),this.ensureScalesHaveIDs(),this.buildOrUpdateScales()}_checkEventBindings(){const t=this.options,e=new Set(Object.keys(this._listeners)),i=new Set(t.events);P(e,i)&&!!this._responsiveListeners===t.responsive||(this.unbindEvents(),this.bindEvents())}_updateHiddenIndices(){const{_hiddenIndices:t}=this,e=this._getUniformDataChanges()||[];for(const{method:i,start:s,count:n}of e){Cn(t,s,"_removeElements"===i?-n:n)}}_getUniformDataChanges(){const t=this._dataChanges;if(!t||!t.length)return;this._dataChanges=[];const e=this.data.datasets.length,i=e=>new Set(t.filter((t=>t[0]===e)).map(((t,e)=>e+","+t.splice(1).join(",")))),s=i(0);for(let t=1;tt.split(","))).map((t=>({method:t[1],start:+t[2],count:+t[3]})))}_updateLayout(t){if(!1===this.notifyPlugins("beforeLayout",{cancelable:!0}))return;as.update(this,this.width,this.height,t);const e=this.chartArea,i=e.width<=0||e.height<=0;this._layers=[],u(this.boxes,(t=>{i&&"chartArea"===t.position||(t.configure&&t.configure(),this._layers.push(...t._layers()))}),this),this._layers.forEach(((t,e)=>{t._idx=e})),this.notifyPlugins("afterLayout")}_updateDatasets(t){if(!1!==this.notifyPlugins("beforeDatasetsUpdate",{mode:t,cancelable:!0})){for(let t=0,e=this.data.datasets.length;t=0;--e)this._drawDataset(t[e]);this.notifyPlugins("afterDatasetsDraw")}_drawDataset(t){const e=this.ctx,i=t._clip,s=!i.disabled,n=function(t,e){const{xScale:i,yScale:s}=t;return i&&s?{left:On(i,e,"left"),right:On(i,e,"right"),top:On(s,e,"top"),bottom:On(s,e,"bottom")}:e}(t,this.chartArea),o={meta:t,index:t.index,cancelable:!0};!1!==this.notifyPlugins("beforeDatasetDraw",o)&&(s&&Ie(e,{left:!1===i.left?0:n.left-i.left,right:!1===i.right?this.width:n.right+i.right,top:!1===i.top?0:n.top-i.top,bottom:!1===i.bottom?this.height:n.bottom+i.bottom}),t.controller.draw(),s&&ze(e),o.cancelable=!1,this.notifyPlugins("afterDatasetDraw",o))}isPointInArea(t){return Re(t,this.chartArea,this._minPadding)}getElementsAtEventForMode(t,e,i,s){const n=Xi.modes[e];return"function"==typeof n?n(this,t,i,s):[]}getDatasetMeta(t){const e=this.data.datasets[t],i=this._metasets;let s=i.filter((t=>t&&t._dataset===e)).pop();return s||(s={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e&&e.order||0,index:t,_dataset:e,_parsed:[],_sorted:!1},i.push(s)),s}getContext(){return this.$context||(this.$context=Ci(null,{chart:this,type:"chart"}))}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(t){const e=this.data.datasets[t];if(!e)return!1;const i=this.getDatasetMeta(t);return"boolean"==typeof i.hidden?!i.hidden:!e.hidden}setDatasetVisibility(t,e){this.getDatasetMeta(t).hidden=!e}toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}getDataVisibility(t){return!this._hiddenIndices[t]}_updateVisibility(t,e,i){const s=i?"show":"hide",n=this.getDatasetMeta(t),o=n.controller._resolveAnimations(void 0,s);k(e)?(n.data[e].hidden=!i,this.update()):(this.setDatasetVisibility(t,i),o.update(n,{visible:i}),this.update((e=>e.datasetIndex===t?s:void 0)))}hide(t,e){this._updateVisibility(t,e,!1)}show(t,e){this._updateVisibility(t,e,!0)}_destroyDatasetMeta(t){const e=this._metasets[t];e&&e.controller&&e.controller._destroy(),delete this._metasets[t]}_stop(){let t,e;for(this.stop(),xt.remove(this),t=0,e=this.data.datasets.length;t{e.addEventListener(this,i,s),t[i]=s},s=(t,e,i)=>{t.offsetX=e,t.offsetY=i,this._eventHandler(t)};u(this.options.events,(t=>i(t,s)))}bindResponsiveEvents(){this._responsiveListeners||(this._responsiveListeners={});const t=this._responsiveListeners,e=this.platform,i=(i,s)=>{e.addEventListener(this,i,s),t[i]=s},s=(i,s)=>{t[i]&&(e.removeEventListener(this,i,s),delete t[i])},n=(t,e)=>{this.canvas&&this.resize(t,e)};let o;const a=()=>{s("attach",a),this.attached=!0,this.resize(),i("resize",n),i("detach",o)};o=()=>{this.attached=!1,s("resize",n),this._stop(),this._resize(0,0),i("attach",a)},e.isAttached(this.canvas)?a():o()}unbindEvents(){u(this._listeners,((t,e)=>{this.platform.removeEventListener(this,e,t)})),this._listeners={},u(this._responsiveListeners,((t,e)=>{this.platform.removeEventListener(this,e,t)})),this._responsiveListeners=void 0}updateHoverStyle(t,e,i){const s=i?"set":"remove";let n,o,a,r;for("dataset"===e&&(n=this.getDatasetMeta(t[0].datasetIndex),n.controller["_"+s+"DatasetHoverStyle"]()),a=0,r=t.length;a{const i=this.getDatasetMeta(t);if(!i)throw new Error("No dataset found at index "+t);return{datasetIndex:t,element:i.data[e],index:e}}));!f(i,e)&&(this._active=i,this._lastEvent=null,this._updateHoverStyles(i,e))}notifyPlugins(t,e,i){return this._plugins.notify(this,t,e,i)}isPluginEnabled(t){return 1===this._plugins._cache.filter((e=>e.plugin.id===t)).length}_updateHoverStyles(t,e,i){const s=this.options.hover,n=(t,e)=>t.filter((t=>!e.some((e=>t.datasetIndex===e.datasetIndex&&t.index===e.index)))),o=n(e,t),a=i?t:n(t,e);o.length&&this.updateHoverStyle(o,s.mode,!1),a.length&&s.mode&&this.updateHoverStyle(a,s.mode,!0)}_eventHandler(t,e){const i={event:t,replay:e,cancelable:!0,inChartArea:this.isPointInArea(t)},s=e=>(e.options.events||this.options.events).includes(t.native.type);if(!1===this.notifyPlugins("beforeEvent",i,s))return;const n=this._handleEvent(t,e,i.inChartArea);return i.cancelable=!1,this.notifyPlugins("afterEvent",i,s),(n||i.changed)&&this.render(),this}_handleEvent(t,e,i){const{_active:s=[],options:n}=this,o=e,a=this._getActiveElements(t,s,i,o),r=D(t),l=function(t,e,i,s){return i&&"mouseout"!==t.type?s?e:t:null}(t,this._lastEvent,i,r);i&&(this._lastEvent=null,d(n.onHover,[t,a,this],this),r&&d(n.onClick,[t,a,this],this));const h=!f(a,s);return(h||e)&&(this._active=a,this._updateHoverStyles(a,s,e)),this._lastEvent=l,h}_getActiveElements(t,e,i,s){if("mouseout"===t.type)return[];if(!i)return e;const n=this.options.hover;return this.getElementsAtEventForMode(t,n.mode,n,s)}}function Tn(){return u(An.instances,(t=>t._plugins.invalidate()))}function Ln(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}class En{static override(t){Object.assign(En.prototype,t)}options;constructor(t){this.options=t||{}}init(){}formats(){return Ln()}parse(){return Ln()}format(){return Ln()}add(){return Ln()}diff(){return Ln()}startOf(){return Ln()}endOf(){return Ln()}}var Rn={_date:En};function In(t){const e=t.iScale,i=function(t,e){if(!t._cache.$bar){const i=t.getMatchingVisibleMetas(e);let s=[];for(let e=0,n=i.length;et-e)))}return t._cache.$bar}(e,t.type);let s,n,o,a,r=e._length;const l=()=>{32767!==o&&-32768!==o&&(k(a)&&(r=Math.min(r,Math.abs(o-a)||r)),a=o)};for(s=0,n=i.length;sMath.abs(r)&&(l=r,h=a),e[i.axis]=h,e._custom={barStart:l,barEnd:h,start:n,end:o,min:a,max:r}}(t,e,i,s):e[i.axis]=i.parse(t,s),e}function Fn(t,e,i,s){const n=t.iScale,o=t.vScale,a=n.getLabels(),r=n===o,l=[];let h,c,d,u;for(h=i,c=i+s;ht.x,i="left",s="right"):(e=t.base"spacing"!==t,_indexable:t=>"spacing"!==t&&!t.startsWith("borderDash")&&!t.startsWith("hoverBorderDash")};static overrides={aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const e=t.data;if(e.labels.length&&e.datasets.length){const{labels:{pointStyle:i,color:s}}=t.legend.options;return e.labels.map(((e,n)=>{const o=t.getDatasetMeta(0).controller.getStyle(n);return{text:e,fillStyle:o.backgroundColor,strokeStyle:o.borderColor,fontColor:s,lineWidth:o.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(n),index:n}}))}return[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}}}};constructor(t,e){super(t,e),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(t,e){const i=this.getDataset().data,s=this._cachedMeta;if(!1===this._parsing)s._parsed=i;else{let n,a,r=t=>+i[t];if(o(i[t])){const{key:t="value"}=this._parsing;r=e=>+M(i[e],t)}for(n=t,a=t+e;nZ(t,r,l,!0)?1:Math.max(e,e*i,s,s*i),g=(t,e,s)=>Z(t,r,l,!0)?-1:Math.min(e,e*i,s,s*i),p=f(0,h,d),m=f(E,c,u),b=g(C,h,d),x=g(C+E,c,u);s=(p-b)/2,n=(m-x)/2,o=-(p+b)/2,a=-(m+x)/2}return{ratioX:s,ratioY:n,offsetX:o,offsetY:a}}(u,d,r),b=(i.width-o)/f,x=(i.height-o)/g,_=Math.max(Math.min(b,x)/2,0),y=c(this.options.radius,_),v=(y-Math.max(y*r,0))/this._getVisibleDatasetWeightTotal();this.offsetX=p*y,this.offsetY=m*y,s.total=this.calculateTotal(),this.outerRadius=y-v*this._getRingWeightOffset(this.index),this.innerRadius=Math.max(this.outerRadius-v*l,0),this.updateElements(n,0,n.length,t)}_circumference(t,e){const i=this.options,s=this._cachedMeta,n=this._getCircumference();return e&&i.animation.animateRotate||!this.chart.getDataVisibility(t)||null===s._parsed[t]||s.data[t].hidden?0:this.calculateCircumference(s._parsed[t]*n/O)}updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=o.chartArea,r=o.options.animation,l=(a.left+a.right)/2,h=(a.top+a.bottom)/2,c=n&&r.animateScale,d=c?0:this.innerRadius,u=c?0:this.outerRadius,{sharedOptions:f,includeOptions:g}=this._getSharedOptions(e,s);let p,m=this._getRotation();for(p=0;p0&&!isNaN(t)?O*(Math.abs(t)/e):0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.labels||[],n=ne(e._parsed[t],i.options.locale);return{label:s[t]||"",value:n}}getMaxBorderWidth(t){let e=0;const i=this.chart;let s,n,o,a,r;if(!t)for(s=0,n=i.data.datasets.length;s{const o=t.getDatasetMeta(0).controller.getStyle(n);return{text:e,fillStyle:o.backgroundColor,strokeStyle:o.borderColor,fontColor:s,lineWidth:o.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(n),index:n}}))}return[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}}},scales:{r:{type:"radialLinear",angleLines:{display:!1},beginAtZero:!0,grid:{circular:!0},pointLabels:{display:!1},startAngle:0}}};constructor(t,e){super(t,e),this.innerRadius=void 0,this.outerRadius=void 0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.labels||[],n=ne(e._parsed[t].r,i.options.locale);return{label:s[t]||"",value:n}}parseObjectData(t,e,i,s){return ii.bind(this)(t,e,i,s)}update(t){const e=this._cachedMeta.data;this._updateRadius(),this.updateElements(e,0,e.length,t)}getMinMax(){const t=this._cachedMeta,e={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY};return t.data.forEach(((t,i)=>{const s=this.getParsed(i).r;!isNaN(s)&&this.chart.getDataVisibility(i)&&(se.max&&(e.max=s))})),e}_updateRadius(){const t=this.chart,e=t.chartArea,i=t.options,s=Math.min(e.right-e.left,e.bottom-e.top),n=Math.max(s/2,0),o=(n-Math.max(i.cutoutPercentage?n/100*i.cutoutPercentage:1,0))/t.getVisibleDatasetCount();this.outerRadius=n-o*this.index,this.innerRadius=this.outerRadius-o}updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=o.options.animation,r=this._cachedMeta.rScale,l=r.xCenter,h=r.yCenter,c=r.getIndexAngle(0)-.5*C;let d,u=c;const f=360/this.countVisibleElements();for(d=0;d{!isNaN(this.getParsed(i).r)&&this.chart.getDataVisibility(i)&&e++})),e}_computeAngle(t,e,i){return this.chart.getDataVisibility(t)?$(this.resolveDataElementOptions(t,e).angle||i):0}}var Yn=Object.freeze({__proto__:null,BarController:class extends Ns{static id="bar";static defaults={datasetElementType:!1,dataElementType:"bar",categoryPercentage:.8,barPercentage:.9,grouped:!0,animations:{numbers:{type:"number",properties:["x","y","base","width","height"]}}};static overrides={scales:{_index_:{type:"category",offset:!0,grid:{offset:!0}},_value_:{type:"linear",beginAtZero:!0}}};parsePrimitiveData(t,e,i,s){return Fn(t,e,i,s)}parseArrayData(t,e,i,s){return Fn(t,e,i,s)}parseObjectData(t,e,i,s){const{iScale:n,vScale:o}=t,{xAxisKey:a="x",yAxisKey:r="y"}=this._parsing,l="x"===n.axis?a:r,h="x"===o.axis?a:r,c=[];let d,u,f,g;for(d=i,u=i+s;dt.controller.options.grouped)),o=i.options.stacked,a=[],r=this._cachedMeta.controller.getParsed(e),l=r&&r[i.axis],h=t=>{const e=t._parsed.find((t=>t[i.axis]===l)),n=e&&e[t.vScale.axis];if(s(n)||isNaN(n))return!0};for(const i of n)if((void 0===e||!h(i))&&((!1===o||-1===a.indexOf(i.stack)||void 0===o&&void 0===i.stack)&&a.push(i.stack),i.index===t))break;return a.length||a.push(void 0),a}_getStackCount(t){return this._getStacks(void 0,t).length}_getStackIndex(t,e,i){const s=this._getStacks(t,i),n=void 0!==e?s.indexOf(e):-1;return-1===n?s.length-1:n}_getRuler(){const t=this.options,e=this._cachedMeta,i=e.iScale,s=[];let n,o;for(n=0,o=e.data.length;n=i?1:-1)}(u,e,r)*a,f===r&&(b-=u/2);const t=e.getPixelForDecimal(0),s=e.getPixelForDecimal(1),o=Math.min(t,s),h=Math.max(t,s);b=Math.max(Math.min(b,h),o),d=b+u,i&&!c&&(l._stacks[e.axis]._visualValues[n]=e.getValueForPixel(d)-e.getValueForPixel(b))}if(b===e.getPixelForValue(r)){const t=F(u)*e.getLineWidthForValue(r)/2;b+=t,u-=t}return{size:u,base:b,head:d,center:d+u/2}}_calculateBarIndexPixels(t,e){const i=e.scale,n=this.options,o=n.skipNull,a=l(n.maxBarThickness,1/0);let r,h;if(e.grouped){const i=o?this._getStackCount(t):e.stackCount,l="flex"===n.barThickness?function(t,e,i,s){const n=e.pixels,o=n[t];let a=t>0?n[t-1]:null,r=t=0;--i)e=Math.max(e,t[i].size(this.resolveDataElementOptions(i))/2);return e>0&&e}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart.data.labels||[],{xScale:s,yScale:n}=e,o=this.getParsed(t),a=s.getLabelForValue(o.x),r=n.getLabelForValue(o.y),l=o._custom;return{label:i[t]||"",value:"("+a+", "+r+(l?", "+l:"")+")"}}update(t){const e=this._cachedMeta.data;this.updateElements(e,0,e.length,t)}updateElements(t,e,i,s){const n="reset"===s,{iScale:o,vScale:a}=this._cachedMeta,{sharedOptions:r,includeOptions:l}=this._getSharedOptions(e,s),h=o.axis,c=a.axis;for(let d=e;d0&&this.getParsed(e-1);for(let i=0;i<_;++i){const g=t[i],_=b?g:{};if(i=x){_.skip=!0;continue}const v=this.getParsed(i),M=s(v[f]),w=_[u]=a.getPixelForValue(v[u],i),k=_[f]=o||M?r.getBasePixel():r.getPixelForValue(l?this.applyStack(r,v,l):v[f],i);_.skip=isNaN(w)||isNaN(k)||M,_.stop=i>0&&Math.abs(v[u]-y[u])>m,p&&(_.parsed=v,_.raw=h.data[i]),d&&(_.options=c||this.resolveDataElementOptions(i,g.active?"active":n)),b||this.updateElement(g,i,_,n),y=v}}getMaxOverflow(){const t=this._cachedMeta,e=t.dataset,i=e.options&&e.options.borderWidth||0,s=t.data||[];if(!s.length)return i;const n=s[0].size(this.resolveDataElementOptions(0)),o=s[s.length-1].size(this.resolveDataElementOptions(s.length-1));return Math.max(i,n,o)/2}draw(){const t=this._cachedMeta;t.dataset.updateControlPoints(this.chart.chartArea,t.iScale.axis),super.draw()}},PieController:class extends jn{static id="pie";static defaults={cutout:0,rotation:0,circumference:360,radius:"100%"}},PolarAreaController:$n,RadarController:class extends Ns{static id="radar";static defaults={datasetElementType:"line",dataElementType:"point",indexAxis:"r",showLine:!0,elements:{line:{fill:"start"}}};static overrides={aspectRatio:1,scales:{r:{type:"radialLinear"}}};getLabelAndValue(t){const e=this._cachedMeta.vScale,i=this.getParsed(t);return{label:e.getLabels()[t],value:""+e.getLabelForValue(i[e.axis])}}parseObjectData(t,e,i,s){return ii.bind(this)(t,e,i,s)}update(t){const e=this._cachedMeta,i=e.dataset,s=e.data||[],n=e.iScale.getLabels();if(i.points=s,"resize"!==t){const e=this.resolveDatasetElementOptions(t);this.options.showLine||(e.borderWidth=0);const o={_loop:!0,_fullLoop:n.length===s.length,options:e};this.updateElement(i,void 0,o,t)}this.updateElements(s,0,s.length,t)}updateElements(t,e,i,s){const n=this._cachedMeta.rScale,o="reset"===s;for(let a=e;a0&&this.getParsed(e-1);for(let c=e;c0&&Math.abs(i[f]-_[f])>b,m&&(p.parsed=i,p.raw=h.data[c]),u&&(p.options=d||this.resolveDataElementOptions(c,e.active?"active":n)),x||this.updateElement(e,c,p,n),_=i}this.updateSharedOptions(d,n,c)}getMaxOverflow(){const t=this._cachedMeta,e=t.data||[];if(!this.options.showLine){let t=0;for(let i=e.length-1;i>=0;--i)t=Math.max(t,e[i].size(this.resolveDataElementOptions(i))/2);return t>0&&t}const i=t.dataset,s=i.options&&i.options.borderWidth||0;if(!e.length)return s;const n=e[0].size(this.resolveDataElementOptions(0)),o=e[e.length-1].size(this.resolveDataElementOptions(e.length-1));return Math.max(s,n,o)/2}}});function Un(t,e,i,s){const n=vi(t.options.borderRadius,["outerStart","outerEnd","innerStart","innerEnd"]);const o=(i-e)/2,a=Math.min(o,s*e/2),r=t=>{const e=(i-Math.min(o,t))*s/2;return J(t,0,Math.min(o,e))};return{outerStart:r(n.outerStart),outerEnd:r(n.outerEnd),innerStart:J(n.innerStart,0,a),innerEnd:J(n.innerEnd,0,a)}}function Xn(t,e,i,s){return{x:i+t*Math.cos(e),y:s+t*Math.sin(e)}}function qn(t,e,i,s,n,o){const{x:a,y:r,startAngle:l,pixelMargin:h,innerRadius:c}=e,d=Math.max(e.outerRadius+s+i-h,0),u=c>0?c+s+i+h:0;let f=0;const g=n-l;if(s){const t=((c>0?c-s:0)+(d>0?d-s:0))/2;f=(g-(0!==t?g*t/(t+s):g))/2}const p=(g-Math.max(.001,g*d-i/C)/d)/2,m=l+p+f,b=n-p-f,{outerStart:x,outerEnd:_,innerStart:y,innerEnd:v}=Un(e,u,d,b-m),M=d-x,w=d-_,k=m+x/M,S=b-_/w,P=u+y,D=u+v,O=m+y/P,A=b-v/D;if(t.beginPath(),o){const e=(k+S)/2;if(t.arc(a,r,d,k,e),t.arc(a,r,d,e,S),_>0){const e=Xn(w,S,a,r);t.arc(e.x,e.y,_,S,b+E)}const i=Xn(D,b,a,r);if(t.lineTo(i.x,i.y),v>0){const e=Xn(D,A,a,r);t.arc(e.x,e.y,v,b+E,A+Math.PI)}const s=(b-v/u+(m+y/u))/2;if(t.arc(a,r,u,b-v/u,s,!0),t.arc(a,r,u,s,m+y/u,!0),y>0){const e=Xn(P,O,a,r);t.arc(e.x,e.y,y,O+Math.PI,m-E)}const n=Xn(M,m,a,r);if(t.lineTo(n.x,n.y),x>0){const e=Xn(M,k,a,r);t.arc(e.x,e.y,x,m-E,k)}}else{t.moveTo(a,r);const e=Math.cos(k)*d+a,i=Math.sin(k)*d+r;t.lineTo(e,i);const s=Math.cos(S)*d+a,n=Math.sin(S)*d+r;t.lineTo(s,n)}t.closePath()}function Kn(t,e,i,s,n){const{fullCircles:o,startAngle:a,circumference:r,options:l}=e,{borderWidth:h,borderJoinStyle:c,borderDash:d,borderDashOffset:u}=l,f="inner"===l.borderAlign;if(!h)return;t.setLineDash(d||[]),t.lineDashOffset=u,f?(t.lineWidth=2*h,t.lineJoin=c||"round"):(t.lineWidth=h,t.lineJoin=c||"bevel");let g=e.endAngle;if(o){qn(t,e,i,s,g,n);for(let e=0;en?(h=n/l,t.arc(o,a,l,i+h,s-h,!0)):t.arc(o,a,n,i+E,s-E),t.closePath(),t.clip()}(t,e,g),o||(qn(t,e,i,s,g,n),t.stroke())}function Gn(t,e,i=e){t.lineCap=l(i.borderCapStyle,e.borderCapStyle),t.setLineDash(l(i.borderDash,e.borderDash)),t.lineDashOffset=l(i.borderDashOffset,e.borderDashOffset),t.lineJoin=l(i.borderJoinStyle,e.borderJoinStyle),t.lineWidth=l(i.borderWidth,e.borderWidth),t.strokeStyle=l(i.borderColor,e.borderColor)}function Zn(t,e,i){t.lineTo(i.x,i.y)}function Jn(t,e,i={}){const s=t.length,{start:n=0,end:o=s-1}=i,{start:a,end:r}=e,l=Math.max(n,a),h=Math.min(o,r),c=nr&&o>r;return{count:s,start:l,loop:e.loop,ilen:h(a+(h?r-t:t))%o,_=()=>{f!==g&&(t.lineTo(m,g),t.lineTo(m,f),t.lineTo(m,p))};for(l&&(d=n[x(0)],t.moveTo(d.x,d.y)),c=0;c<=r;++c){if(d=n[x(c)],d.skip)continue;const e=d.x,i=d.y,s=0|e;s===u?(ig&&(g=i),m=(b*m+e)/++b):(_(),t.lineTo(e,i),u=s,b=0,f=g=i),p=i}_()}function eo(t){const e=t.options,i=e.borderDash&&e.borderDash.length;return!(t._decimated||t._loop||e.tension||"monotone"===e.cubicInterpolationMode||e.stepped||i)?to:Qn}const io="function"==typeof Path2D;function so(t,e,i,s){io&&!e.options.segment?function(t,e,i,s){let n=e._path;n||(n=e._path=new Path2D,e.path(n,i,s)&&n.closePath()),Gn(t,e.options),t.stroke(n)}(t,e,i,s):function(t,e,i,s){const{segments:n,options:o}=e,a=eo(e);for(const r of n)Gn(t,o,r.style),t.beginPath(),a(t,e,r,{start:i,end:i+s-1})&&t.closePath(),t.stroke()}(t,e,i,s)}class no extends Hs{static id="line";static defaults={borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",borderWidth:3,capBezierPoints:!0,cubicInterpolationMode:"default",fill:!1,spanGaps:!1,stepped:!1,tension:0};static defaultRoutes={backgroundColor:"backgroundColor",borderColor:"borderColor"};static descriptors={_scriptable:!0,_indexable:t=>"borderDash"!==t&&"fill"!==t};constructor(t){super(),this.animated=!0,this.options=void 0,this._chart=void 0,this._loop=void 0,this._fullLoop=void 0,this._path=void 0,this._points=void 0,this._segments=void 0,this._decimated=!1,this._pointsUpdated=!1,this._datasetIndex=void 0,t&&Object.assign(this,t)}updateControlPoints(t,e){const i=this.options;if((i.tension||"monotone"===i.cubicInterpolationMode)&&!i.stepped&&!this._pointsUpdated){const s=i.spanGaps?this._loop:this._fullLoop;hi(this._points,i,t,s,e),this._pointsUpdated=!0}}set points(t){this._points=t,delete this._segments,delete this._path,this._pointsUpdated=!1}get points(){return this._points}get segments(){return this._segments||(this._segments=zi(this,this.options.segment))}first(){const t=this.segments,e=this.points;return t.length&&e[t[0].start]}last(){const t=this.segments,e=this.points,i=t.length;return i&&e[t[i-1].end]}interpolate(t,e){const i=this.options,s=t[e],n=this.points,o=Ii(this,{property:e,start:s,end:s});if(!o.length)return;const a=[],r=function(t){return t.stepped?pi:t.tension||"monotone"===t.cubicInterpolationMode?mi:gi}(i);let l,h;for(l=0,h=o.length;l"borderDash"!==t};circumference;endAngle;fullCircles;innerRadius;outerRadius;pixelMargin;startAngle;constructor(t){super(),this.options=void 0,this.circumference=void 0,this.startAngle=void 0,this.endAngle=void 0,this.innerRadius=void 0,this.outerRadius=void 0,this.pixelMargin=0,this.fullCircles=0,t&&Object.assign(this,t)}inRange(t,e,i){const s=this.getProps(["x","y"],i),{angle:n,distance:o}=X(s,{x:t,y:e}),{startAngle:a,endAngle:r,innerRadius:h,outerRadius:c,circumference:d}=this.getProps(["startAngle","endAngle","innerRadius","outerRadius","circumference"],i),u=(this.options.spacing+this.options.borderWidth)/2,f=l(d,r-a),g=Z(n,a,r)&&a!==r,p=f>=O||g,m=tt(o,h+u,c+u);return p&&m}getCenterPoint(t){const{x:e,y:i,startAngle:s,endAngle:n,innerRadius:o,outerRadius:a}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius"],t),{offset:r,spacing:l}=this.options,h=(s+n)/2,c=(o+a+l+r)/2;return{x:e+Math.cos(h)*c,y:i+Math.sin(h)*c}}tooltipPosition(t){return this.getCenterPoint(t)}draw(t){const{options:e,circumference:i}=this,s=(e.offset||0)/4,n=(e.spacing||0)/2,o=e.circular;if(this.pixelMargin="inner"===e.borderAlign?.33:0,this.fullCircles=i>O?Math.floor(i/O):0,0===i||this.innerRadius<0||this.outerRadius<0)return;t.save();const a=(this.startAngle+this.endAngle)/2;t.translate(Math.cos(a)*s,Math.sin(a)*s);const r=s*(1-Math.sin(Math.min(C,i||0)));t.fillStyle=e.backgroundColor,t.strokeStyle=e.borderColor,function(t,e,i,s,n){const{fullCircles:o,startAngle:a,circumference:r}=e;let l=e.endAngle;if(o){qn(t,e,i,s,l,n);for(let e=0;e("string"==typeof e?(i=t.push(e)-1,s.unshift({index:i,label:e})):isNaN(e)&&(i=null),i))(t,e,i,s);return n!==t.lastIndexOf(e)?i:n}function po(t){const e=this.getLabels();return t>=0&&ts=e?s:t,a=t=>n=i?n:t;if(t){const t=F(s),e=F(n);t<0&&e<0?a(0):t>0&&e>0&&o(0)}if(s===n){let e=0===n?1:Math.abs(.05*n);a(n+e),t||o(s-e)}this.min=s,this.max=n}getTickLimit(){const t=this.options.ticks;let e,{maxTicksLimit:i,stepSize:s}=t;return s?(e=Math.ceil(this.max/s)-Math.floor(this.min/s)+1,e>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${s} would result generating up to ${e} ticks. Limiting to 1000.`),e=1e3)):(e=this.computeTickLimit(),i=i||11),i&&(e=Math.min(i,e)),e}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){const t=this.options,e=t.ticks;let i=this.getTickLimit();i=Math.max(2,i);const n=function(t,e){const i=[],{bounds:n,step:o,min:a,max:r,precision:l,count:h,maxTicks:c,maxDigits:d,includeBounds:u}=t,f=o||1,g=c-1,{min:p,max:m}=e,b=!s(a),x=!s(r),_=!s(h),y=(m-p)/(d+1);let v,M,w,k,S=B((m-p)/g/f)*f;if(S<1e-14&&!b&&!x)return[{value:p},{value:m}];k=Math.ceil(m/S)-Math.floor(p/S),k>g&&(S=B(k*S/g/f)*f),s(l)||(v=Math.pow(10,l),S=Math.ceil(S*v)/v),"ticks"===n?(M=Math.floor(p/S)*S,w=Math.ceil(m/S)*S):(M=p,w=m),b&&x&&o&&H((r-a)/o,S/1e3)?(k=Math.round(Math.min((r-a)/S,c)),S=(r-a)/k,M=a,w=r):_?(M=b?a:M,w=x?r:w,k=h-1,S=(w-M)/k):(k=(w-M)/S,k=V(k,Math.round(k),S/1e3)?Math.round(k):Math.ceil(k));const P=Math.max(U(S),U(M));v=Math.pow(10,s(l)?P:l),M=Math.round(M*v)/v,w=Math.round(w*v)/v;let D=0;for(b&&(u&&M!==a?(i.push({value:a}),Mr)break;i.push({value:t})}return x&&u&&w!==r?i.length&&V(i[i.length-1].value,r,mo(r,y,t))?i[i.length-1].value=r:i.push({value:r}):x&&w!==r||i.push({value:w}),i}({maxTicks:i,bounds:t.bounds,min:t.min,max:t.max,precision:e.precision,step:e.stepSize,count:e.count,maxDigits:this._maxDigits(),horizontal:this.isHorizontal(),minRotation:e.minRotation||0,includeBounds:!1!==e.includeBounds},this._range||this);return"ticks"===t.bounds&&j(n,this,"value"),t.reverse?(n.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),n}configure(){const t=this.ticks;let e=this.min,i=this.max;if(super.configure(),this.options.offset&&t.length){const s=(i-e)/Math.max(t.length-1,1)/2;e-=s,i+=s}this._startValue=e,this._endValue=i,this._valueRange=i-e}getLabelForValue(t){return ne(t,this.chart.options.locale,this.options.ticks.format)}}class xo extends bo{static id="linear";static defaults={ticks:{callback:ae.formatters.numeric}};determineDataLimits(){const{min:t,max:e}=this.getMinMax(!0);this.min=a(t)?t:0,this.max=a(e)?e:1,this.handleTickRangeOptions()}computeTickLimit(){const t=this.isHorizontal(),e=t?this.width:this.height,i=$(this.options.ticks.minRotation),s=(t?Math.sin(i):Math.cos(i))||.001,n=this._resolveTickFontOptions(0);return Math.ceil(e/Math.min(40,n.lineHeight/s))}getPixelForValue(t){return null===t?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getValueForPixel(t){return this._startValue+this.getDecimalForPixel(t)*this._valueRange}}const _o=t=>Math.floor(z(t)),yo=(t,e)=>Math.pow(10,_o(t)+e);function vo(t){return 1===t/Math.pow(10,_o(t))}function Mo(t,e,i){const s=Math.pow(10,i),n=Math.floor(t/s);return Math.ceil(e/s)-n}function wo(t,{min:e,max:i}){e=r(t.min,e);const s=[],n=_o(e);let o=function(t,e){let i=_o(e-t);for(;Mo(t,e,i)>10;)i++;for(;Mo(t,e,i)<10;)i--;return Math.min(i,_o(t))}(e,i),a=o<0?Math.pow(10,Math.abs(o)):1;const l=Math.pow(10,o),h=n>o?Math.pow(10,n):0,c=Math.round((e-h)*a)/a,d=Math.floor((e-h)/l/10)*l*10;let u=Math.floor((c-d)/Math.pow(10,o)),f=r(t.min,Math.round((h+d+u*Math.pow(10,o))*a)/a);for(;f=10?u=u<15?15:20:u++,u>=20&&(o++,u=2,a=o>=0?1:a),f=Math.round((h+d+u*Math.pow(10,o))*a)/a;const g=r(t.max,f);return s.push({value:g,major:vo(g),significand:u}),s}class ko extends Js{static id="logarithmic";static defaults={ticks:{callback:ae.formatters.logarithmic,major:{enabled:!0}}};constructor(t){super(t),this.start=void 0,this.end=void 0,this._startValue=void 0,this._valueRange=0}parse(t,e){const i=bo.prototype.parse.apply(this,[t,e]);if(0!==i)return a(i)&&i>0?i:null;this._zero=!0}determineDataLimits(){const{min:t,max:e}=this.getMinMax(!0);this.min=a(t)?Math.max(0,t):null,this.max=a(e)?Math.max(0,e):null,this.options.beginAtZero&&(this._zero=!0),this._zero&&this.min!==this._suggestedMin&&!a(this._userMin)&&(this.min=t===yo(this.min,0)?yo(this.min,-1):yo(this.min,0)),this.handleTickRangeOptions()}handleTickRangeOptions(){const{minDefined:t,maxDefined:e}=this.getUserBounds();let i=this.min,s=this.max;const n=e=>i=t?i:e,o=t=>s=e?s:t;i===s&&(i<=0?(n(1),o(10)):(n(yo(i,-1)),o(yo(s,1)))),i<=0&&n(yo(s,-1)),s<=0&&o(yo(i,1)),this.min=i,this.max=s}buildTicks(){const t=this.options,e=wo({min:this._userMin,max:this._userMax},this);return"ticks"===t.bounds&&j(e,this,"value"),t.reverse?(e.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),e}getLabelForValue(t){return void 0===t?"0":ne(t,this.chart.options.locale,this.options.ticks.format)}configure(){const t=this.min;super.configure(),this._startValue=z(t),this._valueRange=z(this.max)-z(t)}getPixelForValue(t){return void 0!==t&&0!==t||(t=this.min),null===t||isNaN(t)?NaN:this.getPixelForDecimal(t===this.min?0:(z(t)-this._startValue)/this._valueRange)}getValueForPixel(t){const e=this.getDecimalForPixel(t);return Math.pow(10,this._startValue+e*this._valueRange)}}function So(t){const e=t.ticks;if(e.display&&t.display){const t=ki(e.backdropPadding);return l(e.font&&e.font.size,ue.font.size)+t.height}return 0}function Po(t,e,i,s,n){return t===s||t===n?{start:e-i/2,end:e+i/2}:tn?{start:e-i,end:e}:{start:e,end:e+i}}function Do(t){const e={l:t.left+t._padding.left,r:t.right-t._padding.right,t:t.top+t._padding.top,b:t.bottom-t._padding.bottom},i=Object.assign({},e),s=[],o=[],a=t._pointLabels.length,r=t.options.pointLabels,l=r.centerPointLabels?C/a:0;for(let u=0;ue.r&&(r=(s.end-e.r)/o,t.r=Math.max(t.r,e.r+r)),n.starte.b&&(l=(n.end-e.b)/a,t.b=Math.max(t.b,e.b+l))}function Oo(t,e,i){const s=t.drawingArea,{extra:n,additionalAngle:o,padding:a,size:r}=i,l=t.getPointPosition(e,s+n+a,o),h=Math.round(Y(G(l.angle+E))),c=function(t,e,i){90===i||270===i?t-=e/2:(i>270||i<90)&&(t-=e);return t}(l.y,r.h,h),d=function(t){if(0===t||180===t)return"center";if(t<180)return"left";return"right"}(h),u=function(t,e,i){"right"===i?t-=e:"center"===i&&(t-=e/2);return t}(l.x,r.w,d);return{visible:!0,x:l.x,y:c,textAlign:d,left:u,top:c,right:u+r.w,bottom:c+r.h}}function Ao(t,e){if(!e)return!0;const{left:i,top:s,right:n,bottom:o}=t;return!(Re({x:i,y:s},e)||Re({x:i,y:o},e)||Re({x:n,y:s},e)||Re({x:n,y:o},e))}function To(t,e,i){const{left:n,top:o,right:a,bottom:r}=i,{backdropColor:l}=e;if(!s(l)){const i=wi(e.borderRadius),s=ki(e.backdropPadding);t.fillStyle=l;const h=n-s.left,c=o-s.top,d=a-n+s.width,u=r-o+s.height;Object.values(i).some((t=>0!==t))?(t.beginPath(),He(t,{x:h,y:c,w:d,h:u,radius:i}),t.fill()):t.fillRect(h,c,d,u)}}function Lo(t,e,i,s){const{ctx:n}=t;if(i)n.arc(t.xCenter,t.yCenter,e,0,O);else{let i=t.getPointPosition(0,e);n.moveTo(i.x,i.y);for(let o=1;ot,padding:5,centerPointLabels:!1}};static defaultRoutes={"angleLines.color":"borderColor","pointLabels.color":"color","ticks.color":"color"};static descriptors={angleLines:{_fallback:"grid"}};constructor(t){super(t),this.xCenter=void 0,this.yCenter=void 0,this.drawingArea=void 0,this._pointLabels=[],this._pointLabelItems=[]}setDimensions(){const t=this._padding=ki(So(this.options)/2),e=this.width=this.maxWidth-t.width,i=this.height=this.maxHeight-t.height;this.xCenter=Math.floor(this.left+e/2+t.left),this.yCenter=Math.floor(this.top+i/2+t.top),this.drawingArea=Math.floor(Math.min(e,i)/2)}determineDataLimits(){const{min:t,max:e}=this.getMinMax(!1);this.min=a(t)&&!isNaN(t)?t:0,this.max=a(e)&&!isNaN(e)?e:0,this.handleTickRangeOptions()}computeTickLimit(){return Math.ceil(this.drawingArea/So(this.options))}generateTickLabels(t){bo.prototype.generateTickLabels.call(this,t),this._pointLabels=this.getLabels().map(((t,e)=>{const i=d(this.options.pointLabels.callback,[t,e],this);return i||0===i?i:""})).filter(((t,e)=>this.chart.getDataVisibility(e)))}fit(){const t=this.options;t.display&&t.pointLabels.display?Do(this):this.setCenterPoint(0,0,0,0)}setCenterPoint(t,e,i,s){this.xCenter+=Math.floor((t-e)/2),this.yCenter+=Math.floor((i-s)/2),this.drawingArea-=Math.min(this.drawingArea/2,Math.max(t,e,i,s))}getIndexAngle(t){return G(t*(O/(this._pointLabels.length||1))+$(this.options.startAngle||0))}getDistanceFromCenterForValue(t){if(s(t))return NaN;const e=this.drawingArea/(this.max-this.min);return this.options.reverse?(this.max-t)*e:(t-this.min)*e}getValueForDistanceFromCenter(t){if(s(t))return NaN;const e=t/(this.drawingArea/(this.max-this.min));return this.options.reverse?this.max-e:this.min+e}getPointLabelContext(t){const e=this._pointLabels||[];if(t>=0&&t=0;n--){const e=t._pointLabelItems[n];if(!e.visible)continue;const o=s.setContext(t.getPointLabelContext(n));To(i,o,e);const a=Si(o.font),{x:r,y:l,textAlign:h}=e;Ne(i,t._pointLabels[n],r,l+a.lineHeight/2,a,{color:o.color,textAlign:h,textBaseline:"middle"})}}(this,o),s.display&&this.ticks.forEach(((t,e)=>{if(0!==e||0===e&&this.min<0){r=this.getDistanceFromCenterForValue(t.value);const i=this.getContext(e),a=s.setContext(i),l=n.setContext(i);!function(t,e,i,s,n){const o=t.ctx,a=e.circular,{color:r,lineWidth:l}=e;!a&&!s||!r||!l||i<0||(o.save(),o.strokeStyle=r,o.lineWidth=l,o.setLineDash(n.dash||[]),o.lineDashOffset=n.dashOffset,o.beginPath(),Lo(t,i,a,s),o.closePath(),o.stroke(),o.restore())}(this,a,r,o,l)}})),i.display){for(t.save(),a=o-1;a>=0;a--){const s=i.setContext(this.getPointLabelContext(a)),{color:n,lineWidth:o}=s;o&&n&&(t.lineWidth=o,t.strokeStyle=n,t.setLineDash(s.borderDash),t.lineDashOffset=s.borderDashOffset,r=this.getDistanceFromCenterForValue(e.reverse?this.min:this.max),l=this.getPointPosition(a,r),t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(l.x,l.y),t.stroke())}t.restore()}}drawBorder(){}drawLabels(){const t=this.ctx,e=this.options,i=e.ticks;if(!i.display)return;const s=this.getIndexAngle(0);let n,o;t.save(),t.translate(this.xCenter,this.yCenter),t.rotate(s),t.textAlign="center",t.textBaseline="middle",this.ticks.forEach(((s,a)=>{if(0===a&&this.min>=0&&!e.reverse)return;const r=i.setContext(this.getContext(a)),l=Si(r.font);if(n=this.getDistanceFromCenterForValue(this.ticks[a].value),r.showLabelBackdrop){t.font=l.string,o=t.measureText(s.label).width,t.fillStyle=r.backdropColor;const e=ki(r.backdropPadding);t.fillRect(-o/2-e.left,-n-l.size/2-e.top,o+e.width,l.size+e.height)}Ne(t,s.label,0,-n,l,{color:r.color,strokeColor:r.textStrokeColor,strokeWidth:r.textStrokeWidth})})),t.restore()}drawTitle(){}}const Ro={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},Io=Object.keys(Ro);function zo(t,e){return t-e}function Fo(t,e){if(s(e))return null;const i=t._adapter,{parser:n,round:o,isoWeekday:r}=t._parseOpts;let l=e;return"function"==typeof n&&(l=n(l)),a(l)||(l="string"==typeof n?i.parse(l,n):i.parse(l)),null===l?null:(o&&(l="week"!==o||!N(r)&&!0!==r?i.startOf(l,o):i.startOf(l,"isoWeek",r)),+l)}function Vo(t,e,i,s){const n=Io.length;for(let o=Io.indexOf(t);o=e?i[s]:i[n]]=!0}}else t[e]=!0}function Wo(t,e,i){const s=[],n={},o=e.length;let a,r;for(a=0;a=0&&(e[l].major=!0);return e}(t,s,n,i):s}class No extends Js{static id="time";static defaults={bounds:"data",adapters:{},time:{parser:!1,unit:!1,round:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{source:"auto",callback:!1,major:{enabled:!1}}};constructor(t){super(t),this._cache={data:[],labels:[],all:[]},this._unit="day",this._majorUnit=void 0,this._offsets={},this._normalized=!1,this._parseOpts=void 0}init(t,e={}){const i=t.time||(t.time={}),s=this._adapter=new Rn._date(t.adapters.date);s.init(e),x(i.displayFormats,s.formats()),this._parseOpts={parser:i.parser,round:i.round,isoWeekday:i.isoWeekday},super.init(t),this._normalized=e.normalized}parse(t,e){return void 0===t?null:Fo(this,t)}beforeLayout(){super.beforeLayout(),this._cache={data:[],labels:[],all:[]}}determineDataLimits(){const t=this.options,e=this._adapter,i=t.time.unit||"day";let{min:s,max:n,minDefined:o,maxDefined:r}=this.getUserBounds();function l(t){o||isNaN(t.min)||(s=Math.min(s,t.min)),r||isNaN(t.max)||(n=Math.max(n,t.max))}o&&r||(l(this._getLabelBounds()),"ticks"===t.bounds&&"labels"===t.ticks.source||l(this.getMinMax(!1))),s=a(s)&&!isNaN(s)?s:+e.startOf(Date.now(),i),n=a(n)&&!isNaN(n)?n:+e.endOf(Date.now(),i)+1,this.min=Math.min(s,n-1),this.max=Math.max(s+1,n)}_getLabelBounds(){const t=this.getLabelTimestamps();let e=Number.POSITIVE_INFINITY,i=Number.NEGATIVE_INFINITY;return t.length&&(e=t[0],i=t[t.length-1]),{min:e,max:i}}buildTicks(){const t=this.options,e=t.time,i=t.ticks,s="labels"===i.source?this.getLabelTimestamps():this._generate();"ticks"===t.bounds&&s.length&&(this.min=this._userMin||s[0],this.max=this._userMax||s[s.length-1]);const n=this.min,o=nt(s,n,this.max);return this._unit=e.unit||(i.autoSkip?Vo(e.minUnit,this.min,this.max,this._getLabelCapacity(n)):function(t,e,i,s,n){for(let o=Io.length-1;o>=Io.indexOf(i);o--){const i=Io[o];if(Ro[i].common&&t._adapter.diff(n,s,i)>=e-1)return i}return Io[i?Io.indexOf(i):0]}(this,o.length,e.minUnit,this.min,this.max)),this._majorUnit=i.major.enabled&&"year"!==this._unit?function(t){for(let e=Io.indexOf(t)+1,i=Io.length;e+t.value)))}initOffsets(t=[]){let e,i,s=0,n=0;this.options.offset&&t.length&&(e=this.getDecimalForValue(t[0]),s=1===t.length?1-e:(this.getDecimalForValue(t[1])-e)/2,i=this.getDecimalForValue(t[t.length-1]),n=1===t.length?i:(i-this.getDecimalForValue(t[t.length-2]))/2);const o=t.length<3?.5:.25;s=J(s,0,o),n=J(n,0,o),this._offsets={start:s,end:n,factor:1/(s+1+n)}}_generate(){const t=this._adapter,e=this.min,i=this.max,s=this.options,n=s.time,o=n.unit||Vo(n.minUnit,e,i,this._getLabelCapacity(e)),a=l(s.ticks.stepSize,1),r="week"===o&&n.isoWeekday,h=N(r)||!0===r,c={};let d,u,f=e;if(h&&(f=+t.startOf(f,"isoWeek",r)),f=+t.startOf(f,h?"day":o),t.diff(i,e,o)>1e5*a)throw new Error(e+" and "+i+" are too far apart with stepSize of "+a+" "+o);const g="data"===s.ticks.source&&this.getDataTimestamps();for(d=f,u=0;d+t))}getLabelForValue(t){const e=this._adapter,i=this.options.time;return i.tooltipFormat?e.format(t,i.tooltipFormat):e.format(t,i.displayFormats.datetime)}format(t,e){const i=this.options.time.displayFormats,s=this._unit,n=e||i[s];return this._adapter.format(t,n)}_tickFormatFunction(t,e,i,s){const n=this.options,o=n.ticks.callback;if(o)return d(o,[t,e,i],this);const a=n.time.displayFormats,r=this._unit,l=this._majorUnit,h=r&&a[r],c=l&&a[l],u=i[e],f=l&&c&&u&&u.major;return this._adapter.format(t,s||(f?c:h))}generateTickLabels(t){let e,i,s;for(e=0,i=t.length;e0?a:1}getDataTimestamps(){let t,e,i=this._cache.data||[];if(i.length)return i;const s=this.getMatchingVisibleMetas();if(this._normalized&&s.length)return this._cache.data=s[0].controller.getAllParsedValues(this);for(t=0,e=s.length;t=t[r].pos&&e<=t[l].pos&&({lo:r,hi:l}=it(t,"pos",e)),({pos:s,time:o}=t[r]),({pos:n,time:a}=t[l])):(e>=t[r].time&&e<=t[l].time&&({lo:r,hi:l}=it(t,"time",e)),({time:s,pos:o}=t[r]),({time:n,pos:a}=t[l]));const h=n-s;return h?o+(a-o)*(e-s)/h:o}var jo=Object.freeze({__proto__:null,CategoryScale:class extends Js{static id="category";static defaults={ticks:{callback:po}};constructor(t){super(t),this._startValue=void 0,this._valueRange=0,this._addedLabels=[]}init(t){const e=this._addedLabels;if(e.length){const t=this.getLabels();for(const{index:i,label:s}of e)t[i]===s&&t.splice(i,1);this._addedLabels=[]}super.init(t)}parse(t,e){if(s(t))return null;const i=this.getLabels();return((t,e)=>null===t?null:J(Math.round(t),0,e))(e=isFinite(e)&&i[e]===t?e:go(i,t,l(e,t),this._addedLabels),i.length-1)}determineDataLimits(){const{minDefined:t,maxDefined:e}=this.getUserBounds();let{min:i,max:s}=this.getMinMax(!0);"ticks"===this.options.bounds&&(t||(i=0),e||(s=this.getLabels().length-1)),this.min=i,this.max=s}buildTicks(){const t=this.min,e=this.max,i=this.options.offset,s=[];let n=this.getLabels();n=0===t&&e===n.length-1?n:n.slice(t,e+1),this._valueRange=Math.max(n.length-(i?0:1),1),this._startValue=this.min-(i?.5:0);for(let i=t;i<=e;i++)s.push({value:i});return s}getLabelForValue(t){return po.call(this,t)}configure(){super.configure(),this.isHorizontal()||(this._reversePixels=!this._reversePixels)}getPixelForValue(t){return"number"!=typeof t&&(t=this.parse(t)),null===t?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getValueForPixel(t){return Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange)}getBasePixel(){return this.bottom}},LinearScale:xo,LogarithmicScale:ko,RadialLinearScale:Eo,TimeScale:No,TimeSeriesScale:class extends No{static id="timeseries";static defaults=No.defaults;constructor(t){super(t),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){const t=this._getTimestampsForTable(),e=this._table=this.buildLookupTable(t);this._minPos=Ho(e,this.min),this._tableRange=Ho(e,this.max)-this._minPos,super.initOffsets(t)}buildLookupTable(t){const{min:e,max:i}=this,s=[],n=[];let o,a,r,l,h;for(o=0,a=t.length;o=e&&l<=i&&s.push(l);if(s.length<2)return[{time:e,pos:0},{time:i,pos:1}];for(o=0,a=s.length;ot-e))}_getTimestampsForTable(){let t=this._cache.all||[];if(t.length)return t;const e=this.getDataTimestamps(),i=this.getLabelTimestamps();return t=e.length&&i.length?this.normalize(e.concat(i)):e.length?e:i,t=this._cache.all=t,t}getDecimalForValue(t){return(Ho(this._table,t)-this._minPos)/this._tableRange}getValueForPixel(t){const e=this._offsets,i=this.getDecimalForPixel(t)/e.factor-e.end;return Ho(this._table,i*this._tableRange+this._minPos,!0)}}});const $o=["rgb(54, 162, 235)","rgb(255, 99, 132)","rgb(255, 159, 64)","rgb(255, 205, 86)","rgb(75, 192, 192)","rgb(153, 102, 255)","rgb(201, 203, 207)"],Yo=$o.map((t=>t.replace("rgb(","rgba(").replace(")",", 0.5)")));function Uo(t){return $o[t%$o.length]}function Xo(t){return Yo[t%Yo.length]}function qo(t){let e=0;return(i,s)=>{const n=t.getDatasetMeta(s).controller;n instanceof jn?e=function(t,e){return t.backgroundColor=t.data.map((()=>Uo(e++))),e}(i,e):n instanceof $n?e=function(t,e){return t.backgroundColor=t.data.map((()=>Xo(e++))),e}(i,e):n&&(e=function(t,e){return t.borderColor=Uo(e),t.backgroundColor=Xo(e),++e}(i,e))}}function Ko(t){let e;for(e in t)if(t[e].borderColor||t[e].backgroundColor)return!0;return!1}var Go={id:"colors",defaults:{enabled:!0,forceOverride:!1},beforeLayout(t,e,i){if(!i.enabled)return;const{data:{datasets:s},options:n}=t.config,{elements:o}=n,a=Ko(s)||(r=n)&&(r.borderColor||r.backgroundColor)||o&&Ko(o)||"rgba(0,0,0,0.1)"!==ue.borderColor||"rgba(0,0,0,0.1)"!==ue.backgroundColor;var r;if(!i.forceOverride&&a)return;const l=qo(t);s.forEach(l)}};function Zo(t){if(t._decimated){const e=t._data;delete t._decimated,delete t._data,Object.defineProperty(t,"data",{configurable:!0,enumerable:!0,writable:!0,value:e})}}function Jo(t){t.data.datasets.forEach((t=>{Zo(t)}))}var Qo={id:"decimation",defaults:{algorithm:"min-max",enabled:!1},beforeElementsUpdate:(t,e,i)=>{if(!i.enabled)return void Jo(t);const n=t.width;t.data.datasets.forEach(((e,o)=>{const{_data:a,indexAxis:r}=e,l=t.getDatasetMeta(o),h=a||e.data;if("y"===Pi([r,t.options.indexAxis]))return;if(!l.controller.supportsDecimation)return;const c=t.scales[l.xAxisID];if("linear"!==c.type&&"time"!==c.type)return;if(t.options.parsing)return;let{start:d,count:u}=function(t,e){const i=e.length;let s,n=0;const{iScale:o}=t,{min:a,max:r,minDefined:l,maxDefined:h}=o.getUserBounds();return l&&(n=J(it(e,o.axis,a).lo,0,i-1)),s=h?J(it(e,o.axis,r).hi+1,n,i)-n:i-n,{start:n,count:s}}(l,h);if(u<=(i.threshold||4*n))return void Zo(e);let f;switch(s(a)&&(e._data=h,delete e.data,Object.defineProperty(e,"data",{configurable:!0,enumerable:!0,get:function(){return this._decimated},set:function(t){this._data=t}})),i.algorithm){case"lttb":f=function(t,e,i,s,n){const o=n.samples||s;if(o>=i)return t.slice(e,e+i);const a=[],r=(i-2)/(o-2);let l=0;const h=e+i-1;let c,d,u,f,g,p=e;for(a[l++]=t[p],c=0;cu&&(u=f,d=t[s],g=s);a[l++]=d,p=g}return a[l++]=t[h],a}(h,d,u,n,i);break;case"min-max":f=function(t,e,i,n){let o,a,r,l,h,c,d,u,f,g,p=0,m=0;const b=[],x=e+i-1,_=t[e].x,y=t[x].x-_;for(o=e;og&&(g=l,d=o),p=(m*p+a.x)/++m;else{const i=o-1;if(!s(c)&&!s(d)){const e=Math.min(c,d),s=Math.max(c,d);e!==u&&e!==i&&b.push({...t[e],x:p}),s!==u&&s!==i&&b.push({...t[s],x:p})}o>0&&i!==u&&b.push(t[i]),b.push(a),h=e,m=0,f=g=l,c=d=u=o}}return b}(h,d,u,n);break;default:throw new Error(`Unsupported decimation algorithm '${i.algorithm}'`)}e._decimated=f}))},destroy(t){Jo(t)}};function ta(t,e,i,s){if(s)return;let n=e[t],o=i[t];return"angle"===t&&(n=G(n),o=G(o)),{property:t,start:n,end:o}}function ea(t,e,i){for(;e>t;e--){const t=i[e];if(!isNaN(t.x)&&!isNaN(t.y))break}return e}function ia(t,e,i,s){return t&&e?s(t[i],e[i]):t?t[i]:e?e[i]:0}function sa(t,e){let i=[],s=!1;return n(t)?(s=!0,i=t):i=function(t,e){const{x:i=null,y:s=null}=t||{},n=e.points,o=[];return e.segments.forEach((({start:t,end:e})=>{e=ea(t,e,n);const a=n[t],r=n[e];null!==s?(o.push({x:a.x,y:s}),o.push({x:r.x,y:s})):null!==i&&(o.push({x:i,y:a.y}),o.push({x:i,y:r.y}))})),o}(t,e),i.length?new no({points:i,options:{tension:0},_loop:s,_fullLoop:s}):null}function na(t){return t&&!1!==t.fill}function oa(t,e,i){let s=t[e].fill;const n=[e];let o;if(!i)return s;for(;!1!==s&&-1===n.indexOf(s);){if(!a(s))return s;if(o=t[s],!o)return!1;if(o.visible)return s;n.push(s),s=o.fill}return!1}function aa(t,e,i){const s=function(t){const e=t.options,i=e.fill;let s=l(i&&i.target,i);void 0===s&&(s=!!e.backgroundColor);if(!1===s||null===s)return!1;if(!0===s)return"origin";return s}(t);if(o(s))return!isNaN(s.value)&&s;let n=parseFloat(s);return a(n)&&Math.floor(n)===n?function(t,e,i,s){"-"!==t&&"+"!==t||(i=e+i);if(i===e||i<0||i>=s)return!1;return i}(s[0],e,n,i):["origin","start","end","stack","shape"].indexOf(s)>=0&&s}function ra(t,e,i){const s=[];for(let n=0;n=0;--e){const i=n[e].$filler;i&&(i.line.updateControlPoints(o,i.axis),s&&i.fill&&da(t.ctx,i,o))}},beforeDatasetsDraw(t,e,i){if("beforeDatasetsDraw"!==i.drawTime)return;const s=t.getSortedVisibleDatasetMetas();for(let e=s.length-1;e>=0;--e){const i=s[e].$filler;na(i)&&da(t.ctx,i,t.chartArea)}},beforeDatasetDraw(t,e,i){const s=e.meta.$filler;na(s)&&"beforeDatasetDraw"===i.drawTime&&da(t.ctx,s,t.chartArea)},defaults:{propagate:!0,drawTime:"beforeDatasetDraw"}};const ba=(t,e)=>{let{boxHeight:i=e,boxWidth:s=e}=t;return t.usePointStyle&&(i=Math.min(i,e),s=t.pointStyleWidth||Math.min(s,e)),{boxWidth:s,boxHeight:i,itemHeight:Math.max(e,i)}};class xa extends Hs{constructor(t){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e,i){this.maxWidth=t,this.maxHeight=e,this._margins=i,this.setDimensions(),this.buildLabels(),this.fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.left=this._margins.left,this.right=this.width):(this.height=this.maxHeight,this.top=this._margins.top,this.bottom=this.height)}buildLabels(){const t=this.options.labels||{};let e=d(t.generateLabels,[this.chart],this)||[];t.filter&&(e=e.filter((e=>t.filter(e,this.chart.data)))),t.sort&&(e=e.sort(((e,i)=>t.sort(e,i,this.chart.data)))),this.options.reverse&&e.reverse(),this.legendItems=e}fit(){const{options:t,ctx:e}=this;if(!t.display)return void(this.width=this.height=0);const i=t.labels,s=Si(i.font),n=s.size,o=this._computeTitleHeight(),{boxWidth:a,itemHeight:r}=ba(i,n);let l,h;e.font=s.string,this.isHorizontal()?(l=this.maxWidth,h=this._fitRows(o,n,a,r)+10):(h=this.maxHeight,l=this._fitCols(o,s,a,r)+10),this.width=Math.min(l,t.maxWidth||this.maxWidth),this.height=Math.min(h,t.maxHeight||this.maxHeight)}_fitRows(t,e,i,s){const{ctx:n,maxWidth:o,options:{labels:{padding:a}}}=this,r=this.legendHitBoxes=[],l=this.lineWidths=[0],h=s+a;let c=t;n.textAlign="left",n.textBaseline="middle";let d=-1,u=-h;return this.legendItems.forEach(((t,f)=>{const g=i+e/2+n.measureText(t.text).width;(0===f||l[l.length-1]+g+2*a>o)&&(c+=h,l[l.length-(f>0?0:1)]=0,u+=h,d++),r[f]={left:0,top:u,row:d,width:g,height:s},l[l.length-1]+=g+a})),c}_fitCols(t,e,i,s){const{ctx:n,maxHeight:o,options:{labels:{padding:a}}}=this,r=this.legendHitBoxes=[],l=this.columnSizes=[],h=o-t;let c=a,d=0,u=0,f=0,g=0;return this.legendItems.forEach(((t,o)=>{const{itemWidth:p,itemHeight:m}=function(t,e,i,s,n){const o=function(t,e,i,s){let n=t.text;n&&"string"!=typeof n&&(n=n.reduce(((t,e)=>t.length>e.length?t:e)));return e+i.size/2+s.measureText(n).width}(s,t,e,i),a=function(t,e,i){let s=t;"string"!=typeof e.text&&(s=_a(e,i));return s}(n,s,e.lineHeight);return{itemWidth:o,itemHeight:a}}(i,e,n,t,s);o>0&&u+m+2*a>h&&(c+=d+a,l.push({width:d,height:u}),f+=d+a,g++,d=u=0),r[o]={left:f,top:u,col:g,width:p,height:m},d=Math.max(d,p),u+=m+a})),c+=d,l.push({width:d,height:u}),c}adjustHitBoxes(){if(!this.options.display)return;const t=this._computeTitleHeight(),{legendHitBoxes:e,options:{align:i,labels:{padding:s},rtl:n}}=this,o=Oi(n,this.left,this.width);if(this.isHorizontal()){let n=0,a=ft(i,this.left+s,this.right-this.lineWidths[n]);for(const r of e)n!==r.row&&(n=r.row,a=ft(i,this.left+s,this.right-this.lineWidths[n])),r.top+=this.top+t+s,r.left=o.leftForLtr(o.x(a),r.width),a+=r.width+s}else{let n=0,a=ft(i,this.top+t+s,this.bottom-this.columnSizes[n].height);for(const r of e)r.col!==n&&(n=r.col,a=ft(i,this.top+t+s,this.bottom-this.columnSizes[n].height)),r.top=a,r.left+=this.left+s,r.left=o.leftForLtr(o.x(r.left),r.width),a+=r.height+s}}isHorizontal(){return"top"===this.options.position||"bottom"===this.options.position}draw(){if(this.options.display){const t=this.ctx;Ie(t,this),this._draw(),ze(t)}}_draw(){const{options:t,columnSizes:e,lineWidths:i,ctx:s}=this,{align:n,labels:o}=t,a=ue.color,r=Oi(t.rtl,this.left,this.width),h=Si(o.font),{padding:c}=o,d=h.size,u=d/2;let f;this.drawTitle(),s.textAlign=r.textAlign("left"),s.textBaseline="middle",s.lineWidth=.5,s.font=h.string;const{boxWidth:g,boxHeight:p,itemHeight:m}=ba(o,d),b=this.isHorizontal(),x=this._computeTitleHeight();f=b?{x:ft(n,this.left+c,this.right-i[0]),y:this.top+c+x,line:0}:{x:this.left+c,y:ft(n,this.top+x+c,this.bottom-e[0].height),line:0},Ai(this.ctx,t.textDirection);const _=m+c;this.legendItems.forEach(((y,v)=>{s.strokeStyle=y.fontColor,s.fillStyle=y.fontColor;const M=s.measureText(y.text).width,w=r.textAlign(y.textAlign||(y.textAlign=o.textAlign)),k=g+u+M;let S=f.x,P=f.y;r.setWidth(this.width),b?v>0&&S+k+c>this.right&&(P=f.y+=_,f.line++,S=f.x=ft(n,this.left+c,this.right-i[f.line])):v>0&&P+_>this.bottom&&(S=f.x=S+e[f.line].width+c,f.line++,P=f.y=ft(n,this.top+x+c,this.bottom-e[f.line].height));if(function(t,e,i){if(isNaN(g)||g<=0||isNaN(p)||p<0)return;s.save();const n=l(i.lineWidth,1);if(s.fillStyle=l(i.fillStyle,a),s.lineCap=l(i.lineCap,"butt"),s.lineDashOffset=l(i.lineDashOffset,0),s.lineJoin=l(i.lineJoin,"miter"),s.lineWidth=n,s.strokeStyle=l(i.strokeStyle,a),s.setLineDash(l(i.lineDash,[])),o.usePointStyle){const a={radius:p*Math.SQRT2/2,pointStyle:i.pointStyle,rotation:i.rotation,borderWidth:n},l=r.xPlus(t,g/2);Ee(s,a,l,e+u,o.pointStyleWidth&&g)}else{const o=e+Math.max((d-p)/2,0),a=r.leftForLtr(t,g),l=wi(i.borderRadius);s.beginPath(),Object.values(l).some((t=>0!==t))?He(s,{x:a,y:o,w:g,h:p,radius:l}):s.rect(a,o,g,p),s.fill(),0!==n&&s.stroke()}s.restore()}(r.x(S),P,y),S=gt(w,S+g+u,b?S+k:this.right,t.rtl),function(t,e,i){Ne(s,i.text,t,e+m/2,h,{strikethrough:i.hidden,textAlign:r.textAlign(i.textAlign)})}(r.x(S),P,y),b)f.x+=k+c;else if("string"!=typeof y.text){const t=h.lineHeight;f.y+=_a(y,t)+c}else f.y+=_})),Ti(this.ctx,t.textDirection)}drawTitle(){const t=this.options,e=t.title,i=Si(e.font),s=ki(e.padding);if(!e.display)return;const n=Oi(t.rtl,this.left,this.width),o=this.ctx,a=e.position,r=i.size/2,l=s.top+r;let h,c=this.left,d=this.width;if(this.isHorizontal())d=Math.max(...this.lineWidths),h=this.top+l,c=ft(t.align,c,this.right-d);else{const e=this.columnSizes.reduce(((t,e)=>Math.max(t,e.height)),0);h=l+ft(t.align,this.top,this.bottom-e-t.labels.padding-this._computeTitleHeight())}const u=ft(a,c,c+d);o.textAlign=n.textAlign(ut(a)),o.textBaseline="middle",o.strokeStyle=e.color,o.fillStyle=e.color,o.font=i.string,Ne(o,e.text,u,h,i)}_computeTitleHeight(){const t=this.options.title,e=Si(t.font),i=ki(t.padding);return t.display?e.lineHeight+i.height:0}_getLegendItemAt(t,e){let i,s,n;if(tt(t,this.left,this.right)&&tt(e,this.top,this.bottom))for(n=this.legendHitBoxes,i=0;it.chart.options.color,boxWidth:40,padding:10,generateLabels(t){const e=t.data.datasets,{labels:{usePointStyle:i,pointStyle:s,textAlign:n,color:o,useBorderRadius:a,borderRadius:r}}=t.legend.options;return t._getSortedDatasetMetas().map((t=>{const l=t.controller.getStyle(i?0:void 0),h=ki(l.borderWidth);return{text:e[t.index].label,fillStyle:l.backgroundColor,fontColor:o,hidden:!t.visible,lineCap:l.borderCapStyle,lineDash:l.borderDash,lineDashOffset:l.borderDashOffset,lineJoin:l.borderJoinStyle,lineWidth:(h.width+h.height)/4,strokeStyle:l.borderColor,pointStyle:s||l.pointStyle,rotation:l.rotation,textAlign:n||l.textAlign,borderRadius:a&&(r||l.borderRadius),datasetIndex:t.index}}),this)}},title:{color:t=>t.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:t=>!t.startsWith("on"),labels:{_scriptable:t=>!["generateLabels","filter","sort"].includes(t)}}};class va extends Hs{constructor(t){super(),this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e){const i=this.options;if(this.left=0,this.top=0,!i.display)return void(this.width=this.height=this.right=this.bottom=0);this.width=this.right=t,this.height=this.bottom=e;const s=n(i.text)?i.text.length:1;this._padding=ki(i.padding);const o=s*Si(i.font).lineHeight+this._padding.height;this.isHorizontal()?this.height=o:this.width=o}isHorizontal(){const t=this.options.position;return"top"===t||"bottom"===t}_drawArgs(t){const{top:e,left:i,bottom:s,right:n,options:o}=this,a=o.align;let r,l,h,c=0;return this.isHorizontal()?(l=ft(a,i,n),h=e+t,r=n-i):("left"===o.position?(l=i+t,h=ft(a,s,e),c=-.5*C):(l=n-t,h=ft(a,e,s),c=.5*C),r=s-e),{titleX:l,titleY:h,maxWidth:r,rotation:c}}draw(){const t=this.ctx,e=this.options;if(!e.display)return;const i=Si(e.font),s=i.lineHeight/2+this._padding.top,{titleX:n,titleY:o,maxWidth:a,rotation:r}=this._drawArgs(s);Ne(t,e.text,0,0,i,{color:e.color,maxWidth:a,rotation:r,textAlign:ut(e.align),textBaseline:"middle",translation:[n,o]})}}var Ma={id:"title",_element:va,start(t,e,i){!function(t,e){const i=new va({ctx:t.ctx,options:e,chart:t});as.configure(t,i,e),as.addBox(t,i),t.titleBlock=i}(t,i)},stop(t){const e=t.titleBlock;as.removeBox(t,e),delete t.titleBlock},beforeUpdate(t,e,i){const s=t.titleBlock;as.configure(t,s,i),s.options=i},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const wa=new WeakMap;var ka={id:"subtitle",start(t,e,i){const s=new va({ctx:t.ctx,options:i,chart:t});as.configure(t,s,i),as.addBox(t,s),wa.set(t,s)},stop(t){as.removeBox(t,wa.get(t)),wa.delete(t)},beforeUpdate(t,e,i){const s=wa.get(t);as.configure(t,s,i),s.options=i},defaults:{align:"center",display:!1,font:{weight:"normal"},fullSize:!0,padding:0,position:"top",text:"",weight:1500},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const Sa={average(t){if(!t.length)return!1;let e,i,s=new Set,n=0,o=0;for(e=0,i=t.length;et+e))/s.size,y:n/o}},nearest(t,e){if(!t.length)return!1;let i,s,n,o=e.x,a=e.y,r=Number.POSITIVE_INFINITY;for(i=0,s=t.length;i-1?t.split("\n"):t}function Ca(t,e){const{element:i,datasetIndex:s,index:n}=e,o=t.getDatasetMeta(s).controller,{label:a,value:r}=o.getLabelAndValue(n);return{chart:t,label:a,parsed:o.getParsed(n),raw:t.data.datasets[s].data[n],formattedValue:r,dataset:o.getDataset(),dataIndex:n,datasetIndex:s,element:i}}function Oa(t,e){const i=t.chart.ctx,{body:s,footer:n,title:o}=t,{boxWidth:a,boxHeight:r}=e,l=Si(e.bodyFont),h=Si(e.titleFont),c=Si(e.footerFont),d=o.length,f=n.length,g=s.length,p=ki(e.padding);let m=p.height,b=0,x=s.reduce(((t,e)=>t+e.before.length+e.lines.length+e.after.length),0);if(x+=t.beforeBody.length+t.afterBody.length,d&&(m+=d*h.lineHeight+(d-1)*e.titleSpacing+e.titleMarginBottom),x){m+=g*(e.displayColors?Math.max(r,l.lineHeight):l.lineHeight)+(x-g)*l.lineHeight+(x-1)*e.bodySpacing}f&&(m+=e.footerMarginTop+f*c.lineHeight+(f-1)*e.footerSpacing);let _=0;const y=function(t){b=Math.max(b,i.measureText(t).width+_)};return i.save(),i.font=h.string,u(t.title,y),i.font=l.string,u(t.beforeBody.concat(t.afterBody),y),_=e.displayColors?a+2+e.boxPadding:0,u(s,(t=>{u(t.before,y),u(t.lines,y),u(t.after,y)})),_=0,i.font=c.string,u(t.footer,y),i.restore(),b+=p.width,{width:b,height:m}}function Aa(t,e,i,s){const{x:n,width:o}=i,{width:a,chartArea:{left:r,right:l}}=t;let h="center";return"center"===s?h=n<=(r+l)/2?"left":"right":n<=o/2?h="left":n>=a-o/2&&(h="right"),function(t,e,i,s){const{x:n,width:o}=s,a=i.caretSize+i.caretPadding;return"left"===t&&n+o+a>e.width||"right"===t&&n-o-a<0||void 0}(h,t,e,i)&&(h="center"),h}function Ta(t,e,i){const s=i.yAlign||e.yAlign||function(t,e){const{y:i,height:s}=e;return it.height-s/2?"bottom":"center"}(t,i);return{xAlign:i.xAlign||e.xAlign||Aa(t,e,i,s),yAlign:s}}function La(t,e,i,s){const{caretSize:n,caretPadding:o,cornerRadius:a}=t,{xAlign:r,yAlign:l}=i,h=n+o,{topLeft:c,topRight:d,bottomLeft:u,bottomRight:f}=wi(a);let g=function(t,e){let{x:i,width:s}=t;return"right"===e?i-=s:"center"===e&&(i-=s/2),i}(e,r);const p=function(t,e,i){let{y:s,height:n}=t;return"top"===e?s+=i:s-="bottom"===e?n+i:n/2,s}(e,l,h);return"center"===l?"left"===r?g+=h:"right"===r&&(g-=h):"left"===r?g-=Math.max(c,u)+n:"right"===r&&(g+=Math.max(d,f)+n),{x:J(g,0,s.width-e.width),y:J(p,0,s.height-e.height)}}function Ea(t,e,i){const s=ki(i.padding);return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-s.right:t.x+s.left}function Ra(t){return Pa([],Da(t))}function Ia(t,e){const i=e&&e.dataset&&e.dataset.tooltip&&e.dataset.tooltip.callbacks;return i?t.override(i):t}const za={beforeTitle:e,title(t){if(t.length>0){const e=t[0],i=e.chart.data.labels,s=i?i.length:0;if(this&&this.options&&"dataset"===this.options.mode)return e.dataset.label||"";if(e.label)return e.label;if(s>0&&e.dataIndex{const e={before:[],lines:[],after:[]},n=Ia(i,t);Pa(e.before,Da(Fa(n,"beforeLabel",this,t))),Pa(e.lines,Fa(n,"label",this,t)),Pa(e.after,Da(Fa(n,"afterLabel",this,t))),s.push(e)})),s}getAfterBody(t,e){return Ra(Fa(e.callbacks,"afterBody",this,t))}getFooter(t,e){const{callbacks:i}=e,s=Fa(i,"beforeFooter",this,t),n=Fa(i,"footer",this,t),o=Fa(i,"afterFooter",this,t);let a=[];return a=Pa(a,Da(s)),a=Pa(a,Da(n)),a=Pa(a,Da(o)),a}_createItems(t){const e=this._active,i=this.chart.data,s=[],n=[],o=[];let a,r,l=[];for(a=0,r=e.length;at.filter(e,s,n,i)))),t.itemSort&&(l=l.sort(((e,s)=>t.itemSort(e,s,i)))),u(l,(e=>{const i=Ia(t.callbacks,e);s.push(Fa(i,"labelColor",this,e)),n.push(Fa(i,"labelPointStyle",this,e)),o.push(Fa(i,"labelTextColor",this,e))})),this.labelColors=s,this.labelPointStyles=n,this.labelTextColors=o,this.dataPoints=l,l}update(t,e){const i=this.options.setContext(this.getContext()),s=this._active;let n,o=[];if(s.length){const t=Sa[i.position].call(this,s,this._eventPosition);o=this._createItems(i),this.title=this.getTitle(o,i),this.beforeBody=this.getBeforeBody(o,i),this.body=this.getBody(o,i),this.afterBody=this.getAfterBody(o,i),this.footer=this.getFooter(o,i);const e=this._size=Oa(this,i),a=Object.assign({},t,e),r=Ta(this.chart,i,a),l=La(i,a,r,this.chart);this.xAlign=r.xAlign,this.yAlign=r.yAlign,n={opacity:1,x:l.x,y:l.y,width:e.width,height:e.height,caretX:t.x,caretY:t.y}}else 0!==this.opacity&&(n={opacity:0});this._tooltipItems=o,this.$context=void 0,n&&this._resolveAnimations().update(this,n),t&&i.external&&i.external.call(this,{chart:this.chart,tooltip:this,replay:e})}drawCaret(t,e,i,s){const n=this.getCaretPosition(t,i,s);e.lineTo(n.x1,n.y1),e.lineTo(n.x2,n.y2),e.lineTo(n.x3,n.y3)}getCaretPosition(t,e,i){const{xAlign:s,yAlign:n}=this,{caretSize:o,cornerRadius:a}=i,{topLeft:r,topRight:l,bottomLeft:h,bottomRight:c}=wi(a),{x:d,y:u}=t,{width:f,height:g}=e;let p,m,b,x,_,y;return"center"===n?(_=u+g/2,"left"===s?(p=d,m=p-o,x=_+o,y=_-o):(p=d+f,m=p+o,x=_-o,y=_+o),b=p):(m="left"===s?d+Math.max(r,h)+o:"right"===s?d+f-Math.max(l,c)-o:this.caretX,"top"===n?(x=u,_=x-o,p=m-o,b=m+o):(x=u+g,_=x+o,p=m+o,b=m-o),y=x),{x1:p,x2:m,x3:b,y1:x,y2:_,y3:y}}drawTitle(t,e,i){const s=this.title,n=s.length;let o,a,r;if(n){const l=Oi(i.rtl,this.x,this.width);for(t.x=Ea(this,i.titleAlign,i),e.textAlign=l.textAlign(i.titleAlign),e.textBaseline="middle",o=Si(i.titleFont),a=i.titleSpacing,e.fillStyle=i.titleColor,e.font=o.string,r=0;r0!==t))?(t.beginPath(),t.fillStyle=n.multiKeyBackground,He(t,{x:e,y:g,w:h,h:l,radius:r}),t.fill(),t.stroke(),t.fillStyle=a.backgroundColor,t.beginPath(),He(t,{x:i,y:g+1,w:h-2,h:l-2,radius:r}),t.fill()):(t.fillStyle=n.multiKeyBackground,t.fillRect(e,g,h,l),t.strokeRect(e,g,h,l),t.fillStyle=a.backgroundColor,t.fillRect(i,g+1,h-2,l-2))}t.fillStyle=this.labelTextColors[i]}drawBody(t,e,i){const{body:s}=this,{bodySpacing:n,bodyAlign:o,displayColors:a,boxHeight:r,boxWidth:l,boxPadding:h}=i,c=Si(i.bodyFont);let d=c.lineHeight,f=0;const g=Oi(i.rtl,this.x,this.width),p=function(i){e.fillText(i,g.x(t.x+f),t.y+d/2),t.y+=d+n},m=g.textAlign(o);let b,x,_,y,v,M,w;for(e.textAlign=o,e.textBaseline="middle",e.font=c.string,t.x=Ea(this,m,i),e.fillStyle=i.bodyColor,u(this.beforeBody,p),f=a&&"right"!==m?"center"===o?l/2+h:l+2+h:0,y=0,M=s.length;y0&&e.stroke()}_updateAnimationTarget(t){const e=this.chart,i=this.$animations,s=i&&i.x,n=i&&i.y;if(s||n){const i=Sa[t.position].call(this,this._active,this._eventPosition);if(!i)return;const o=this._size=Oa(this,t),a=Object.assign({},i,this._size),r=Ta(e,t,a),l=La(t,a,r,e);s._to===l.x&&n._to===l.y||(this.xAlign=r.xAlign,this.yAlign=r.yAlign,this.width=o.width,this.height=o.height,this.caretX=i.x,this.caretY=i.y,this._resolveAnimations().update(this,l))}}_willRender(){return!!this.opacity}draw(t){const e=this.options.setContext(this.getContext());let i=this.opacity;if(!i)return;this._updateAnimationTarget(e);const s={width:this.width,height:this.height},n={x:this.x,y:this.y};i=Math.abs(i)<.001?0:i;const o=ki(e.padding),a=this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length;e.enabled&&a&&(t.save(),t.globalAlpha=i,this.drawBackground(n,t,s,e),Ai(t,e.textDirection),n.y+=o.top,this.drawTitle(n,t,e),this.drawBody(n,t,e),this.drawFooter(n,t,e),Ti(t,e.textDirection),t.restore())}getActiveElements(){return this._active||[]}setActiveElements(t,e){const i=this._active,s=t.map((({datasetIndex:t,index:e})=>{const i=this.chart.getDatasetMeta(t);if(!i)throw new Error("Cannot find a dataset at index "+t);return{datasetIndex:t,element:i.data[e],index:e}})),n=!f(i,s),o=this._positionChanged(s,e);(n||o)&&(this._active=s,this._eventPosition=e,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(t,e,i=!0){if(e&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;const s=this.options,n=this._active||[],o=this._getActiveElements(t,n,e,i),a=this._positionChanged(o,t),r=e||!f(o,n)||a;return r&&(this._active=o,(s.enabled||s.external)&&(this._eventPosition={x:t.x,y:t.y},this.update(!0,e))),r}_getActiveElements(t,e,i,s){const n=this.options;if("mouseout"===t.type)return[];if(!s)return e.filter((t=>this.chart.data.datasets[t.datasetIndex]&&void 0!==this.chart.getDatasetMeta(t.datasetIndex).controller.getParsed(t.index)));const o=this.chart.getElementsAtEventForMode(t,n.mode,n,i);return n.reverse&&o.reverse(),o}_positionChanged(t,e){const{caretX:i,caretY:s,options:n}=this,o=Sa[n.position].call(this,t,e);return!1!==o&&(i!==o.x||s!==o.y)}}var Ba={id:"tooltip",_element:Va,positioners:Sa,afterInit(t,e,i){i&&(t.tooltip=new Va({chart:t,options:i}))},beforeUpdate(t,e,i){t.tooltip&&t.tooltip.initialize(i)},reset(t,e,i){t.tooltip&&t.tooltip.initialize(i)},afterDraw(t){const e=t.tooltip;if(e&&e._willRender()){const i={tooltip:e};if(!1===t.notifyPlugins("beforeTooltipDraw",{...i,cancelable:!0}))return;e.draw(t.ctx),t.notifyPlugins("afterTooltipDraw",i)}},afterEvent(t,e){if(t.tooltip){const i=e.replay;t.tooltip.handleEvent(e.event,i,e.inChartArea)&&(e.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(t,e)=>e.bodyFont.size,boxWidth:(t,e)=>e.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,boxPadding:0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:za},defaultRoutes:{bodyFont:"font",footerFont:"font",titleFont:"font"},descriptors:{_scriptable:t=>"filter"!==t&&"itemSort"!==t&&"external"!==t,_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]};return An.register(Yn,jo,fo,t),An.helpers={...Wi},An._adapters=Rn,An.Animation=Cs,An.Animations=Os,An.animator=xt,An.controllers=en.controllers.items,An.DatasetController=Ns,An.Element=Hs,An.elements=fo,An.Interaction=Xi,An.layouts=as,An.platforms=Ss,An.Scale=Js,An.Ticks=ae,Object.assign(An,Yn,jo,fo,t,Ss),An.Chart=An,"undefined"!=typeof window&&(window.Chart=An),An})); +//# sourceMappingURL=chart.umd.js.map diff --git a/s9pk/.dockerignore b/s9pk/.dockerignore new file mode 100644 index 0000000..61800de --- /dev/null +++ b/s9pk/.dockerignore @@ -0,0 +1,11 @@ +.git +.gitmodules +node_modules +startos +javascript +ncc-cache +assets +docker-images +*.s9pk +app/node_modules +app/data diff --git a/s9pk/.gitignore b/s9pk/.gitignore new file mode 100644 index 0000000..2a1dda7 --- /dev/null +++ b/s9pk/.gitignore @@ -0,0 +1,9 @@ +*.s9pk +startos/*.js +node_modules/ +.DS_Store +.vscode/ +docker-images +javascript +ncc-cache +app/ \ No newline at end of file diff --git a/s9pk/AGENTS.md b/s9pk/AGENTS.md new file mode 100644 index 0000000..fe7e122 --- /dev/null +++ b/s9pk/AGENTS.md @@ -0,0 +1,5 @@ +# AGENTS.md + +You are working in a StartOS service-package repository — a repo that builds a `.s9pk` for installation on StartOS. + +**Before doing anything in this repo, read [CONTRIBUTING.md](./CONTRIBUTING.md) and every document it links to.** That covers what this package is, how it's built, how it ships, and the conventions to follow. Do not begin work until you have read them all. diff --git a/s9pk/CLAUDE.md b/s9pk/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/s9pk/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/s9pk/CONTRIBUTING.md b/s9pk/CONTRIBUTING.md new file mode 100644 index 0000000..8a7c6b0 --- /dev/null +++ b/s9pk/CONTRIBUTING.md @@ -0,0 +1,43 @@ +# Contributing + +## Keep these in sync + +- **[`README.md`](./README.md)** — what this package is and how it's built (image, volumes, interfaces). Technical reference for developers and AI assistants. +- **[`instructions.md`](./instructions.md)** — the user-facing instructions packed into the `.s9pk` and shown on the **Instructions** tab in StartOS, for the person running the service. +- **[`TODO.md`](./TODO.md)** — pending work on this package. + +**Read all three before starting any work.** Any code change that affects user-visible behavior must update `README.md` and `instructions.md` in the same change; add to `TODO.md` when you defer work, and remove items when complete. Content rules: [Writing READMEs](https://docs.start9.com/packaging/writing-readmes.html), [Writing Instructions](https://docs.start9.com/packaging/writing-instructions.html). + +## Environment setup + +See [Environment Setup](https://docs.start9.com/packaging/environment-setup.html) + +## Building + +```bash +npm ci # install dependencies +make # build the universal .s9pk +``` + +For a complete list of build options, see [Makefile](https://docs.start9.com/packaging/makefile.html). + +## Updating the upstream version + +1. Apply the upstream bump per [UPDATING.md](./UPDATING.md). +2. Update `version` and `releaseNotes` in `startos/versions/current.ts` — the latest version always lives in that file, so an in-place edit is all most bumps need. A new file is spun off only when the bump requires a migration — see [Versions](https://docs.start9.com/packaging/versions.html). + +## CI/CD + +Three workflows under `.github/workflows/` wrap reusable workflows in [`start9labs/shared-workflows`](https://github.com/Start9Labs/shared-workflows): + +- **`build.yml`** — on PR, builds the `.s9pk` and uploads per-arch artifacts for sideload testing. +- **`release.yml`** — on `v*` tag, builds per arch and publishes to the test registry. +- **`tagAndRelease.yml`** — on push to `master`, tags `v` and runs `release.yml`, skipping if already in production. + +Promotion to `beta` and `prod` is a separate, manual step. + +## How to contribute + +1. Fork the repository and create a branch from `master`. +2. Make your changes — including the doc updates above. +3. Open a pull request to `master`. diff --git a/s9pk/Dockerfile b/s9pk/Dockerfile new file mode 100644 index 0000000..a075c36 --- /dev/null +++ b/s9pk/Dockerfile @@ -0,0 +1,17 @@ +# Build stage: compile native deps (better-sqlite3) with toolchain present. +FROM node:22-bookworm-slim AS build +WORKDIR /app +RUN apt-get update \ + && apt-get install -y --no-install-recommends python3 make g++ \ + && rm -rf /var/lib/apt/lists/* +COPY app/package.json app/package-lock.json ./ +RUN npm ci --omit=dev +COPY app/ ./ + +# Runtime stage: slim image with prebuilt node_modules copied in. +FROM node:22-bookworm-slim +WORKDIR /app +ENV NODE_ENV=production +COPY --from=build /app ./ +EXPOSE 3000 +CMD ["node", "src/server.js"] diff --git a/s9pk/LICENSE b/s9pk/LICENSE new file mode 100644 index 0000000..599ad42 --- /dev/null +++ b/s9pk/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Start9 Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/s9pk/Makefile b/s9pk/Makefile new file mode 100644 index 0000000..28dda48 --- /dev/null +++ b/s9pk/Makefile @@ -0,0 +1,39 @@ +ARCHES := x86 arm +# overrides to s9pk.mk must precede the include statement + +# Build x86_64 + aarch64 only (matches manifest images.arch). +# The default goal vendors the Node app into ./app (the Docker build context) +# before delegating to s9pk.mk's build matrix. +.DEFAULT_GOAL := default +.PHONY: default prep clean-app + +# Root of the Premier Gunner Node app (this s9pk lives in ./s9pk). +APP_SRC := .. + +default: prep + @$(MAKE) --no-print-directory all + +# Sync app sources into ./app so the Dockerfile can COPY them. node_modules and +# runtime data are excluded; the Dockerfile reinstalls deps via `npm ci`. +prep: + @echo " Vendoring Node app into ./app ..." + @rm -rf app + @mkdir -p app + @rsync -a \ + --exclude='node_modules/' \ + --exclude='data/' \ + --exclude='s9pk/' \ + --exclude='.git/' \ + --exclude='*.log' \ + --exclude='.DS_Store' \ + --exclude='.env' \ + "$(APP_SRC)/package.json" \ + "$(APP_SRC)/package-lock.json" \ + "$(APP_SRC)/src" \ + "$(APP_SRC)/public" \ + app/ + +clean-app: + rm -rf app + +include s9pk.mk diff --git a/s9pk/README.md b/s9pk/README.md new file mode 100644 index 0000000..a046a99 --- /dev/null +++ b/s9pk/README.md @@ -0,0 +1,141 @@ +

+ Hello World Logo +

+ +# Hello World on StartOS + +> **Upstream repo:** + +A minimal reference service for StartOS. It displays a simple web page — nothing more. Use [this repository](https://github.com/Start9Labs/hello-world-startos) as a template when packaging a new service for StartOS. + +## Getting Started + +To learn how to use this template to create your own StartOS service package, see the [Packaging Guide](https://docs.start9.com/packaging). + +--- + +## Table of Contents + +- [Image and Container Runtime](#image-and-container-runtime) +- [Volume and Data Layout](#volume-and-data-layout) +- [Installation and First-Run Flow](#installation-and-first-run-flow) +- [Configuration Management](#configuration-management) +- [Network Access and Interfaces](#network-access-and-interfaces) +- [Actions (StartOS UI)](#actions-startos-ui) +- [Backups and Restore](#backups-and-restore) +- [Health Checks](#health-checks) +- [Dependencies](#dependencies) +- [Limitations and Differences](#limitations-and-differences) +- [What Is Unchanged from Upstream](#what-is-unchanged-from-upstream) +- [Contributing](#contributing) +- [Quick Reference for AI Consumers](#quick-reference-for-ai-consumers) + +--- + +## Image and Container Runtime + +| Property | Value | +| ------------- | -------------------------------------- | +| Image | `ghcr.io/start9labs/hello-world` | +| Architectures | x86_64, aarch64, riscv64 | +| Command | `hello-world` | + +--- + +## Volume and Data Layout + +| Volume | Mount Point | Purpose | +| ------ | ----------- | --------------- | +| `main` | `/data` | Persistent data | + +--- + +## Installation and First-Run Flow + +No special setup. Install and start — the web page is immediately available. + +--- + +## Configuration Management + +No configurable settings. The service runs with no user-facing configuration. + +--- + +## Network Access and Interfaces + +| Interface | Port | Protocol | Purpose | +| --------- | ---- | -------- | -------------------- | +| Web UI | 80 | HTTP | Hello World web page | + +**Access methods:** + +- LAN IP with unique port +- `.local` with unique port +- Tor `.onion` address +- Custom domains (if configured) + +--- + +## Actions (StartOS UI) + +None. + +--- + +## Backups and Restore + +**Included in backup:** + +- `main` volume + +**Restore behavior:** Volume is fully restored before the service starts. + +--- + +## Health Checks + +| Check | Method | Messages | +| ------------- | ------------------- | ------------------------------------------------------------------ | +| Web Interface | Port listening (80) | Success: "The web interface is ready" / Error: "The web interface is not ready" | + +--- + +## Dependencies + +None. + +--- + +## Limitations and Differences + +1. **No meaningful functionality** — this is a reference/template package only + +--- + +## What Is Unchanged from Upstream + +The service is identical to upstream. There are no modifications. + +--- + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for build instructions and development workflow. + +--- + +## Quick Reference for AI Consumers + +```yaml +package_id: hello-world +image: ghcr.io/start9labs/hello-world +architectures: [x86_64, aarch64, riscv64] +volumes: + main: /data +ports: + ui: 80 +dependencies: none +startos_managed_env_vars: none +actions: none +``` diff --git a/s9pk/TODO.md b/s9pk/TODO.md new file mode 100644 index 0000000..4640904 --- /dev/null +++ b/s9pk/TODO.md @@ -0,0 +1 @@ +# TODO diff --git a/s9pk/UPDATING.md b/s9pk/UPDATING.md new file mode 100644 index 0000000..b88604b --- /dev/null +++ b/s9pk/UPDATING.md @@ -0,0 +1,17 @@ +# Updating the upstream version + +This package wraps Start9 Labs' own [hello-world](https://github.com/Start9Labs/hello-world) source, which we build and publish ourselves as `ghcr.io/start9labs/hello-world`. "Upstream" here means that source repo, not the image namespace. + +## Determining the upstream version + +- **hello-world** ([Start9Labs/hello-world](https://github.com/Start9Labs/hello-world)) — fetch the latest release tag: + + ```sh + gh release view -R Start9Labs/hello-world --json tagName -q .tagName + ``` + + The current pin lives in `startos/manifest/index.ts` at `images['hello-world'].source.dockerTag` (the version after the `:` in `ghcr.io/start9labs/hello-world:`). + +## Applying the bump + +- Bump `dockerTag` in `startos/manifest/index.ts` to `ghcr.io/start9labs/hello-world:` (drop the leading `v` from the release tag). diff --git a/s9pk/assets/.gitkeep b/s9pk/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/s9pk/icon.svg b/s9pk/icon.svg new file mode 100644 index 0000000..51785c1 --- /dev/null +++ b/s9pk/icon.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/s9pk/instructions.md b/s9pk/instructions.md new file mode 100644 index 0000000..b8ceddc --- /dev/null +++ b/s9pk/instructions.md @@ -0,0 +1,25 @@ +# Hello World + +You've installed Hello World — there's nothing to configure and nothing to set up. This page covers how to open the page it serves and where to read more. (If you're a developer, Hello World is also the recommended packaging template.) + +## Documentation + +- [Hello World upstream docs](https://github.com/Start9Labs/hello-world/blob/master/README.md) — the README for the web server this package runs. +- [StartOS Packaging Guide](https://docs.start9.com/packaging) — how to build a StartOS service package from that template. + +## What you get on StartOS + +- **A running web server** that serves a single static page. +- **Nothing to configure and no actions** — the service starts on its own and is immediately usable. + +## Getting set up + +There's no setup wizard, no admin password, no first-run prompt — Hello World is usable the moment it starts. To view the page it serves: + +1. Open Hello World's **Dashboard** tab. +2. Click the **Web UI** interface to open the served page in your browser. + +## Limitations + +- Hello World is intentionally minimal. It is not a useful service on its own; it exists to demonstrate the StartOS packaging system. +- The page content is static and cannot be customized through the StartOS UI. diff --git a/s9pk/package-lock.json b/s9pk/package-lock.json new file mode 100644 index 0000000..a068536 --- /dev/null +++ b/s9pk/package-lock.json @@ -0,0 +1,359 @@ +{ + "name": "hello-world-startos", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "hello-world-startos", + "dependencies": { + "@start9labs/start-sdk": "1.5.3" + }, + "devDependencies": { + "@types/node": "^22.19.0", + "@vercel/ncc": "^0.38.4", + "prettier": "^3.6.2", + "typescript": "^5.9.3" + } + }, + "node_modules/@iarna/toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==", + "license": "ISC" + }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodable/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-1.5.3.tgz", + "integrity": "sha512-OyHe9J6hMvyA5ZavcLkxdVQvZcuTH9J9kagV6NDI83eAG/YpJFIq62gP/n/2PPNdHWwNSXVQmSwnsvsV8Gyg+A==", + "license": "MIT", + "dependencies": { + "@iarna/toml": "^3.0.0", + "@noble/curves": "^1.9.7", + "@noble/hashes": "^1.8.0", + "@types/ini": "^4.1.1", + "deep-equality-data-structures": "^2.0.0", + "fast-xml-parser": "~5.7.0", + "ini": "^5.0.0", + "isomorphic-fetch": "^3.0.0", + "mime": "^4.1.0", + "yaml": "^2.8.3", + "zod": "4.3.6", + "zod-deep-partial": "^1.2.0" + } + }, + "node_modules/@types/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-MIyNUZipBTbyUNnhvuXJTY7B6qNI78meck9Jbv3wk0OgNwRyOOVEKDutAkOs1snB/tx0FafyR6/SN4Ps0hZPeg==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", + "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vercel/ncc": { + "version": "0.38.4", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.4.tgz", + "integrity": "sha512-8LwjnlP39s08C08J5NstzriPvW1SP8Zfpp1BvC2sI35kPeZnHfxVkCwu4/+Wodgnd60UtT1n8K8zw+Mp7J9JmQ==", + "dev": true, + "license": "MIT", + "bin": { + "ncc": "dist/ncc/cli.js" + } + }, + "node_modules/deep-equality-data-structures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/deep-equality-data-structures/-/deep-equality-data-structures-2.0.0.tgz", + "integrity": "sha512-qgrUr7MKXq7VRN+WUpQ48QlXVGL0KdibAoTX8KRg18lgOgqbEKMAW1WZsVCtakY4+XX42pbAJzTz/DlXEFM2Fg==", + "license": "MIT", + "dependencies": { + "object-hash": "^3.0.0" + } + }, + "node_modules/fast-xml-builder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.5.0", + "xml-naming": "^0.1.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz", + "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.7", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/ini": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", + "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "node_modules/mime": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", + "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", + "funding": [ + "https://github.com/sponsors/broofa" + ], + "license": "MIT", + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-expression-matcher": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/strnum": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", + "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "license": "MIT" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/xml-naming": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-deep-partial": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/zod-deep-partial/-/zod-deep-partial-1.4.4.tgz", + "integrity": "sha512-aWkPl7hVStgE01WzbbSxCgX4O+sSpgt8JOjvFUtMTF75VgL6MhWQbiZi+AWGN85SfSTtI9gsOtL1vInoqfDVaA==", + "license": "MIT", + "peerDependencies": { + "zod": "^4.1.13" + } + } + } +} diff --git a/s9pk/package.json b/s9pk/package.json new file mode 100644 index 0000000..5a29c51 --- /dev/null +++ b/s9pk/package.json @@ -0,0 +1,23 @@ +{ + "name": "hello-world-startos", + "scripts": { + "build": "rm -rf ./javascript && ncc build startos/index.ts -o ./javascript", + "prettier": "prettier --write startos", + "check": "tsc --noEmit" + }, + "dependencies": { + "@start9labs/start-sdk": "1.5.3" + }, + "devDependencies": { + "@types/node": "^22.19.0", + "@vercel/ncc": "^0.38.4", + "prettier": "^3.6.2", + "typescript": "^5.9.3" + }, + "prettier": { + "trailingComma": "all", + "tabWidth": 2, + "semi": false, + "singleQuote": true + } +} diff --git a/s9pk/s9pk.mk b/s9pk/s9pk.mk new file mode 100644 index 0000000..4c47870 --- /dev/null +++ b/s9pk/s9pk.mk @@ -0,0 +1,138 @@ +# ** Plumbing. DO NOT EDIT **. +# This file is imported by ./Makefile. Make edits there + +PACKAGE_ID := $(shell awk -F"'" '/id:/ {print $$2}' startos/manifest/index.ts) +INGREDIENTS := $(shell start-cli s9pk list-ingredients 2>/dev/null) +# Resolve the actual git dir so this works inside git worktrees, where .git +# is a file pointing at
/.git/worktrees/ rather than a directory. +GIT_DIR := $(shell git rev-parse --git-dir 2>/dev/null) +GIT_DEPS := $(if $(GIT_DIR),$(GIT_DIR)/HEAD $(GIT_DIR)/index) +ARCHES ?= x86 arm riscv +# TARGETS is the list of leaf make-targets the build matrix fans out over. +# Defaults to the arches; variant packages override (e.g. immich, ollama, vllm +# set this to a list of variant or variant-arch leaf targets). +TARGETS ?= $(ARCHES) +ifdef VARIANT +BASE_NAME := $(PACKAGE_ID)_$(VARIANT) +else +BASE_NAME := $(PACKAGE_ID) +endif + +.PHONY: all arches aarch64 x86_64 riscv64 arm arm64 x86 riscv arch/* clean install check-deps check-init package ingredients +.DELETE_ON_ERROR: +.SECONDARY: + +define SUMMARY + @manifest=$$(start-cli s9pk inspect $(1) manifest); \ + size=$$(du -h $(1) | awk '{print $$1}'); \ + title=$$(printf '%s' "$$manifest" | jq -r .title); \ + version=$$(printf '%s' "$$manifest" | jq -r .version); \ + arches=$$(printf '%s' "$$manifest" | jq -r '[.images[].arch // []] | flatten | unique | join(", ")'); \ + sdkv=$$(printf '%s' "$$manifest" | jq -r .sdkVersion); \ + gitHash=$$(printf '%s' "$$manifest" | jq -r .gitHash | sed -E 's/(.*-modified)$$/\x1b[0;31m\1\x1b[0m/'); \ + printf "\n"; \ + printf "\033[1;32m✅ Build Complete!\033[0m\n"; \ + printf "\n"; \ + printf "\033[1;37m📦 $$title\033[0m \033[36mv$$version\033[0m\n"; \ + printf "───────────────────────────────\n"; \ + printf " \033[1;36mFilename:\033[0m %s\n" "$(1)"; \ + printf " \033[1;36mSize:\033[0m %s\n" "$$size"; \ + printf " \033[1;36mArch:\033[0m %s\n" "$$arches"; \ + printf " \033[1;36mSDK:\033[0m %s\n" "$$sdkv"; \ + printf " \033[1;36mGit:\033[0m %s\n" "$$gitHash"; \ + echo "" +endef + +all: $(TARGETS) + +arches: $(ARCHES) + +# Generic make-variable introspection. Used by the release workflow to +# read $(TARGETS) and fan out one matrix runner per target. `make -s +# print-TARGETS` echoes the list with no other output. +print-%: + @echo '$($*)' + +universal: $(BASE_NAME).s9pk + $(call SUMMARY,$<) + +arch/%: $(BASE_NAME)_%.s9pk + $(call SUMMARY,$<) + +x86 x86_64: arch/x86_64 +arm arm64 aarch64: arch/aarch64 +riscv riscv64: arch/riscv64 + +$(BASE_NAME).s9pk: $(INGREDIENTS) $(GIT_DEPS) + @$(MAKE) --no-print-directory ingredients + @echo " Packing '$@'..." + start-cli s9pk pack -o $@ + +$(BASE_NAME)_%.s9pk: $(INGREDIENTS) $(GIT_DEPS) + @$(MAKE) --no-print-directory ingredients + @echo " Packing '$@'..." + start-cli s9pk pack --arch=$* -o $@ + +ingredients: $(INGREDIENTS) + @echo " Re-evaluating ingredients..." + +install: | check-deps check-init + @HOST=$$(awk -F'/' '/^host:/ {print $$3}' ~/.startos/config.yaml); \ + if [ -z "$$HOST" ]; then \ + echo "Error: You must define \"host: http://server-name.local\" in ~/.startos/config.yaml"; \ + exit 1; \ + fi; \ + S9PK=$$(ls -t *.s9pk 2>/dev/null | head -1); \ + if [ -z "$$S9PK" ]; then \ + echo "Error: No .s9pk file found. Run 'make' first."; \ + exit 1; \ + fi; \ + printf "\n🚀 Installing %s to %s ...\n" "$$S9PK" "$$HOST"; \ + start-cli package install -s "$$S9PK" + +publish: | all + @REGISTRY=$$(awk -F'/' '/^registry:/ {print $$3}' ~/.startos/config.yaml); \ + if [ -z "$$REGISTRY" ]; then \ + echo "Error: You must define \"registry: https://my-registry.tld\" in ~/.startos/config.yaml"; \ + exit 1; \ + fi; \ + S3BASE=$$(awk -F'/' '/^s9pk-s3base:/ {print $$3}' ~/.startos/config.yaml); \ + if [ -z "$$S3BASE" ]; then \ + echo "Error: You must define \"s3base: https://s9pks.my-s3-bucket.tld\" in ~/.startos/config.yaml"; \ + exit 1; \ + fi; \ + command -v s3cmd >/dev/null || \ + (echo "Error: s3cmd not found. It must be installed to publish using s3." && exit 1); \ + printf "\n🚀 Publishing to %s; indexing on %s ...\n" "$$S3BASE" "$$REGISTRY"; \ + for s9pk in *.s9pk; do \ + age=$$(( $$(date +%s) - $$(stat -c %Y "$$s9pk") )); \ + if [ "$$age" -gt 3600 ]; then \ + printf "\033[1;33m⚠️ %s is %d minutes old. Publish anyway? [y/N] \033[0m" "$$s9pk" "$$((age / 60))"; \ + read -r ans; \ + case "$$ans" in [yY]*) ;; *) echo "Skipping $$s9pk"; continue ;; esac; \ + fi; \ + start-cli s9pk publish "$$s9pk"; \ + done + +check-deps: + @command -v start-cli >/dev/null || \ + (echo "Error: start-cli not found. Please see https://docs.start9.com/latest/developer-guide/sdk/installing-the-sdk" && exit 1) + @command -v npm >/dev/null || \ + (echo "Error: npm not found. Please install Node.js and npm." && exit 1) + +check-init: + @if [ ! -f ~/.startos/developer.key.pem ]; then \ + echo "Initializing StartOS developer environment..."; \ + start-cli init-key; \ + fi + +javascript/index.js: $(shell find startos -type f) tsconfig.json node_modules + npm run check + npm run build + +node_modules: package-lock.json package.json + npm ci + +clean: + @echo "Cleaning up build artifacts..." + @rm -rf $(PACKAGE_ID).s9pk $(PACKAGE_ID)_x86_64.s9pk $(PACKAGE_ID)_aarch64.s9pk $(PACKAGE_ID)_riscv64.s9pk javascript node_modules diff --git a/s9pk/startos/actions/index.ts b/s9pk/startos/actions/index.ts new file mode 100644 index 0000000..8a6ef89 --- /dev/null +++ b/s9pk/startos/actions/index.ts @@ -0,0 +1,44 @@ +import { store } from '../fileModels/store' +import { i18n } from '../i18n' +import { sdk } from '../sdk' + +const { InputSpec, Value } = sdk + +const inputSpec = InputSpec.of({ + password: Value.text({ + name: i18n('Password'), + description: i18n( + 'The password Gunner types on the login screen (at least 4 characters)', + ), + required: true, + default: null, + masked: true, + minLength: 4, + }), +}) + +export const setPassword = sdk.Action.withInput( + 'set-password', + + async ({ effects }) => ({ + name: i18n('Set Login Password'), + description: i18n('Set the password Gunner uses to log in to Premier Gunner'), + warning: null, + allowedStatuses: 'any', + group: null, + visibility: 'enabled', + }), + + inputSpec, + + async ({ effects }) => { + const password = await store.read((s) => s.password).const(effects) + return { password: password ?? undefined } + }, + + async ({ effects, input }) => { + await store.merge(effects, { password: input.password }) + }, +) + +export const actions = sdk.Actions.of().addAction(setPassword) diff --git a/s9pk/startos/backups.ts b/s9pk/startos/backups.ts new file mode 100644 index 0000000..0a90b1e --- /dev/null +++ b/s9pk/startos/backups.ts @@ -0,0 +1,5 @@ +import { sdk } from './sdk' + +export const { createBackup, restoreInit } = sdk.setupBackups( + async ({ effects }) => sdk.Backups.ofVolumes('main'), +) diff --git a/s9pk/startos/dependencies.ts b/s9pk/startos/dependencies.ts new file mode 100644 index 0000000..7221c4b --- /dev/null +++ b/s9pk/startos/dependencies.ts @@ -0,0 +1,5 @@ +import { sdk } from './sdk' + +export const setDependencies = sdk.setupDependencies( + async ({ effects }) => ({}), +) diff --git a/s9pk/startos/fileModels/README.md b/s9pk/startos/fileModels/README.md new file mode 100644 index 0000000..561420b --- /dev/null +++ b/s9pk/startos/fileModels/README.md @@ -0,0 +1,5 @@ +Use the `/fileModels` directory to create separate `.ts` files to represent underlying files used by you package. The exported `FileModels` afford a convenient and type safe way to read amd write to the underlying files, as well as react to changes. + +Supported file formats are `.yaml`, `.toml`, `.json`, `.env`, `.ini`, and `.txt`. For alternative file formats, you can use the `raw` method and provide custom serialization and parser functions. + +It is common for packages to use a `store.json.ts` FileModel as a convenient place to persist arbitrary data that are needed by the package but are _not_ persisted by the upstream service. For example, you might use store.json to persist startup flags or login credentials. diff --git a/s9pk/startos/fileModels/store.ts b/s9pk/startos/fileModels/store.ts new file mode 100644 index 0000000..551cf65 --- /dev/null +++ b/s9pk/startos/fileModels/store.ts @@ -0,0 +1,13 @@ +import { FileHelper, z } from '@start9labs/start-sdk' +import { sdk } from '../sdk' + +/** + * Persists package data that the upstream app does not store itself. + * Here: the login password Premier Gunner injects into the app as PG_PASSWORD. + */ +export const store = FileHelper.json( + { base: sdk.volumes.main, subpath: 'store.json' }, + z.object({ + password: z.string(), + }), +) diff --git a/s9pk/startos/i18n/dictionaries/default.ts b/s9pk/startos/i18n/dictionaries/default.ts new file mode 100644 index 0000000..318c3c5 --- /dev/null +++ b/s9pk/startos/i18n/dictionaries/default.ts @@ -0,0 +1,26 @@ +export const DEFAULT_LANG = 'en_US' + +const dict = { + // main.ts + 'Starting Premier Gunner!': 0, + 'Web Interface': 1, + 'The web interface is ready': 2, + 'The web interface is not ready': 3, + + // interfaces.ts + 'Premier Gunner': 4, + 'The Premier Gunner training tracker web app': 5, + + // actions + 'Set Login Password': 6, + 'Set the password Gunner uses to log in to Premier Gunner': 7, + 'Password': 8, + 'The password Gunner types on the login screen (at least 4 characters)': 9, +} as const + +/** + * Plumbing. DO NOT EDIT. + */ +export type I18nKey = keyof typeof dict +export type LangDict = Record<(typeof dict)[I18nKey], string> +export default dict diff --git a/s9pk/startos/i18n/dictionaries/translations.ts b/s9pk/startos/i18n/dictionaries/translations.ts new file mode 100644 index 0000000..d7cc135 --- /dev/null +++ b/s9pk/startos/i18n/dictionaries/translations.ts @@ -0,0 +1,52 @@ +import { LangDict } from './default' + +export default { + es_ES: { + 0: '¡Iniciando Premier Gunner!', + 1: 'Interfaz web', + 2: 'La interfaz web está lista', + 3: 'La interfaz web no está lista', + 4: 'Premier Gunner', + 5: 'La aplicación web de seguimiento de entrenamiento Premier Gunner', + 6: 'Establecer contraseña de acceso', + 7: 'Establece la contraseña que Gunner usa para iniciar sesión en Premier Gunner', + 8: 'Contraseña', + 9: 'La contraseña que Gunner escribe en la pantalla de inicio de sesión (al menos 4 caracteres)', + }, + de_DE: { + 0: 'Starte Premier Gunner!', + 1: 'Weboberfläche', + 2: 'Die Weboberfläche ist bereit', + 3: 'Die Weboberfläche ist nicht bereit', + 4: 'Premier Gunner', + 5: 'Die Premier Gunner Trainings-Tracker-Web-App', + 6: 'Anmeldepasswort festlegen', + 7: 'Lege das Passwort fest, mit dem Gunner sich bei Premier Gunner anmeldet', + 8: 'Passwort', + 9: 'Das Passwort, das Gunner auf dem Anmeldebildschirm eingibt (mindestens 4 Zeichen)', + }, + pl_PL: { + 0: 'Uruchamianie Premier Gunner!', + 1: 'Interfejs webowy', + 2: 'Interfejs webowy jest gotowy', + 3: 'Interfejs webowy nie jest gotowy', + 4: 'Premier Gunner', + 5: 'Aplikacja webowa do śledzenia treningów Premier Gunner', + 6: 'Ustaw hasło logowania', + 7: 'Ustaw hasło, którego Gunner używa do logowania w Premier Gunner', + 8: 'Hasło', + 9: 'Hasło, które Gunner wpisuje na ekranie logowania (co najmniej 4 znaki)', + }, + fr_FR: { + 0: 'Démarrage de Premier Gunner !', + 1: 'Interface web', + 2: "L'interface web est prête", + 3: "L'interface web n'est pas prête", + 4: 'Premier Gunner', + 5: "L'application web de suivi d'entraînement Premier Gunner", + 6: 'Définir le mot de passe de connexion', + 7: 'Définissez le mot de passe que Gunner utilise pour se connecter à Premier Gunner', + 8: 'Mot de passe', + 9: "Le mot de passe que Gunner saisit sur l'écran de connexion (au moins 4 caractères)", + }, +} satisfies Record diff --git a/s9pk/startos/i18n/index.ts b/s9pk/startos/i18n/index.ts new file mode 100644 index 0000000..04cea20 --- /dev/null +++ b/s9pk/startos/i18n/index.ts @@ -0,0 +1,8 @@ +/** + * Plumbing. DO NOT EDIT this file. + */ +import { setupI18n } from '@start9labs/start-sdk' +import defaultDict, { DEFAULT_LANG } from './dictionaries/default' +import translations from './dictionaries/translations' + +export const i18n = setupI18n(defaultDict, translations, DEFAULT_LANG) diff --git a/s9pk/startos/index.ts b/s9pk/startos/index.ts new file mode 100644 index 0000000..7af589b --- /dev/null +++ b/s9pk/startos/index.ts @@ -0,0 +1,11 @@ +/** + * Plumbing. DO NOT EDIT. + */ +export { createBackup } from './backups' +export { main } from './main' +export { init, uninit } from './init' +export { actions } from './actions' +import { buildManifest } from '@start9labs/start-sdk' +import { manifest as sdkManifest } from './manifest' +import { versionGraph } from './versions' +export const manifest = buildManifest(versionGraph, sdkManifest) diff --git a/s9pk/startos/init/index.ts b/s9pk/startos/init/index.ts new file mode 100644 index 0000000..2e671e5 --- /dev/null +++ b/s9pk/startos/init/index.ts @@ -0,0 +1,16 @@ +import { sdk } from '../sdk' +import { setDependencies } from '../dependencies' +import { setInterfaces } from '../interfaces' +import { versionGraph } from '../versions' +import { actions } from '../actions' +import { restoreInit } from '../backups' + +export const init = sdk.setupInit( + restoreInit, + versionGraph, + setInterfaces, + setDependencies, + actions, +) + +export const uninit = sdk.setupUninit(versionGraph) diff --git a/s9pk/startos/interfaces.ts b/s9pk/startos/interfaces.ts new file mode 100644 index 0000000..a7c9f08 --- /dev/null +++ b/s9pk/startos/interfaces.ts @@ -0,0 +1,25 @@ +import { i18n } from './i18n' +import { sdk } from './sdk' +import { uiPort } from './utils' + +export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => { + const uiMulti = sdk.MultiHost.of(effects, 'ui-multi') + const uiMultiOrigin = await uiMulti.bindPort(uiPort, { + protocol: 'http', + }) + const ui = sdk.createInterface(effects, { + name: i18n('Premier Gunner'), + id: 'ui', + description: i18n('The Premier Gunner training tracker web app'), + type: 'ui', + masked: false, + schemeOverride: null, + username: null, + path: '', + query: {}, + }) + + const uiReceipt = await uiMultiOrigin.export([ui]) + + return [uiReceipt] +}) diff --git a/s9pk/startos/main.ts b/s9pk/startos/main.ts new file mode 100644 index 0000000..e434a18 --- /dev/null +++ b/s9pk/startos/main.ts @@ -0,0 +1,47 @@ +import { store } from './fileModels/store' +import { i18n } from './i18n' +import { sdk } from './sdk' +import { uiPort } from './utils' + +export const main = sdk.setupMain(async ({ effects }) => { + console.info(i18n('Starting Premier Gunner!')) + + // The login password lives in store.json. Reading it with `.const` makes the + // daemon restart whenever it changes (e.g. via the "Set Login Password" action), + // so PG_PASSWORD stays authoritative on every boot. + const password = await store.read((s) => s.password).const(effects) + + return sdk.Daemons.of(effects).addDaemon('primary', { + subcontainer: await sdk.SubContainer.of( + effects, + { imageId: 'premier-gunner' }, + sdk.Mounts.of().mountVolume({ + volumeId: 'main', + subpath: null, + mountpoint: '/data', + readonly: false, + }), + 'premier-gunner-sub', + ), + exec: { + command: ['node', 'src/server.js'], + cwd: '/app', + env: { + NODE_ENV: 'production', + PG_HOST: '0.0.0.0', + PG_PORT: String(uiPort), + PG_DATA_DIR: '/data', + ...(password ? { PG_PASSWORD: password } : {}), + }, + }, + ready: { + display: i18n('Web Interface'), + fn: () => + sdk.healthCheck.checkPortListening(effects, uiPort, { + successMessage: i18n('The web interface is ready'), + errorMessage: i18n('The web interface is not ready'), + }), + }, + requires: [], + }) +}) diff --git a/s9pk/startos/manifest/i18n.ts b/s9pk/startos/manifest/i18n.ts new file mode 100644 index 0000000..f2c7b3a --- /dev/null +++ b/s9pk/startos/manifest/i18n.ts @@ -0,0 +1,20 @@ +export const short = { + en_US: 'A kid-friendly soccer training tracker', + es_ES: 'Un rastreador de entrenamiento de fútbol para niños', + de_DE: 'Ein kinderfreundlicher Fußball-Trainings-Tracker', + pl_PL: 'Przyjazny dzieciom tracker treningu piłkarskiego', + fr_FR: "Un suivi d'entraînement de football adapté aux enfants", +} + +export const long = { + en_US: + 'Premier Gunner is a kid-friendly, mobile-friendly soccer training tracker. Log daily training across categories, plan future sessions, set goals, and watch progress climb toward the big reward.', + es_ES: + 'Premier Gunner es un rastreador de entrenamiento de fútbol adaptado a niños y móviles. Registra el entrenamiento diario por categorías, planifica sesiones futuras, fija metas y observa el progreso hacia la gran recompensa.', + de_DE: + 'Premier Gunner ist ein kinder- und mobilfreundlicher Fußball-Trainings-Tracker. Erfasse das tägliche Training nach Kategorien, plane zukünftige Einheiten, setze Ziele und verfolge den Fortschritt zur großen Belohnung.', + pl_PL: + 'Premier Gunner to przyjazny dzieciom i urządzeniom mobilnym tracker treningu piłkarskiego. Zapisuj codzienny trening w kategoriach, planuj przyszłe sesje, ustawiaj cele i obserwuj postępy w drodze do wielkiej nagrody.', + fr_FR: + "Premier Gunner est un suivi d'entraînement de football adapté aux enfants et aux mobiles. Enregistrez l'entraînement quotidien par catégories, planifiez les séances futures, fixez des objectifs et suivez les progrès vers la grande récompense.", +} diff --git a/s9pk/startos/manifest/index.ts b/s9pk/startos/manifest/index.ts new file mode 100644 index 0000000..78b71a0 --- /dev/null +++ b/s9pk/startos/manifest/index.ts @@ -0,0 +1,29 @@ +import { setupManifest } from '@start9labs/start-sdk' +import { long, short } from './i18n' + +export const manifest = setupManifest({ + id: 'premier-gunner', + title: 'Premier Gunner', + license: 'MIT', + packageRepo: 'https://github.com/ten31/premier-gunner', + upstreamRepo: 'https://github.com/ten31/premier-gunner', + marketingUrl: 'https://github.com/ten31/premier-gunner', + donationUrl: null, + description: { short, long }, + volumes: ['main'], + images: { + 'premier-gunner': { + source: { dockerBuild: { dockerfile: 'Dockerfile', workdir: '.' } }, + arch: ['x86_64', 'aarch64'], + }, + }, + alerts: { + install: null, + update: null, + uninstall: null, + restore: null, + start: null, + stop: null, + }, + dependencies: {}, +}) diff --git a/s9pk/startos/sdk.ts b/s9pk/startos/sdk.ts new file mode 100644 index 0000000..04ae4b1 --- /dev/null +++ b/s9pk/startos/sdk.ts @@ -0,0 +1,9 @@ +import { StartSdk } from '@start9labs/start-sdk' +import { manifest } from './manifest' + +/** + * Plumbing. DO NOT EDIT. + * + * The exported "sdk" const is used throughout this package codebase. + */ +export const sdk = StartSdk.of().withManifest(manifest).build(true) diff --git a/s9pk/startos/utils.ts b/s9pk/startos/utils.ts new file mode 100644 index 0000000..8d7a550 --- /dev/null +++ b/s9pk/startos/utils.ts @@ -0,0 +1,5 @@ +// Here we define any constants or functions that are shared by multiple components +// throughout the package codebase. + +// The port the Premier Gunner Node server listens on inside the container. +export const uiPort = 3000 diff --git a/s9pk/startos/versions/current.ts b/s9pk/startos/versions/current.ts new file mode 100644 index 0000000..c1cd7e6 --- /dev/null +++ b/s9pk/startos/versions/current.ts @@ -0,0 +1,28 @@ +import { IMPOSSIBLE, utils, VersionInfo } from '@start9labs/start-sdk' +import { store } from '../fileModels/store' + +export const current = VersionInfo.of({ + version: '0.1.0:0', + releaseNotes: { + en_US: 'Initial release of Premier Gunner for StartOS.', + es_ES: 'Versión inicial de Premier Gunner para StartOS.', + de_DE: 'Erste Veröffentlichung von Premier Gunner für StartOS.', + pl_PL: 'Pierwsze wydanie Premier Gunner dla StartOS.', + fr_FR: 'Première version de Premier Gunner pour StartOS.', + }, + migrations: { + up: async ({ effects }) => { + // Generate a random login password on first install so the app is never + // left on a known default. The user can change it via "Set Login Password". + const existing = await store.read().once() + if (!existing) { + const password = utils.getDefaultString({ + charset: 'a-z,A-Z,2-9', + len: 16, + }) + await store.write(effects, { password }) + } + }, + down: IMPOSSIBLE, + }, +}) diff --git a/s9pk/startos/versions/index.ts b/s9pk/startos/versions/index.ts new file mode 100644 index 0000000..e596b0c --- /dev/null +++ b/s9pk/startos/versions/index.ts @@ -0,0 +1,7 @@ +import { VersionGraph } from '@start9labs/start-sdk' +import { current } from './current' + +export const versionGraph = VersionGraph.of({ + current, + other: [], +}) diff --git a/s9pk/tsconfig.json b/s9pk/tsconfig.json new file mode 100644 index 0000000..a2945a5 --- /dev/null +++ b/s9pk/tsconfig.json @@ -0,0 +1,11 @@ +{ + "include": ["startos/**/*.ts", "node_modules/**/startos"], + "compilerOptions": { + "target": "ES2018", + "module": "CommonJS", + "moduleResolution": "node", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true + } +} diff --git a/src/auth.js b/src/auth.js new file mode 100644 index 0000000..a7a4dff --- /dev/null +++ b/src/auth.js @@ -0,0 +1,88 @@ +import bcrypt from 'bcryptjs'; +import { randomBytes } from 'node:crypto'; +import { db, getSetting, setSetting } from './db.js'; +import { config } from './config.js'; + +export const COOKIE_NAME = 'pg_session'; + +// Resolve a stable cookie-signing secret (persisted so sessions survive restarts). +export function getCookieSecret() { + if (config.cookieSecret) return config.cookieSecret; + let secret = getSetting('cookie_secret'); + if (!secret) { + secret = randomBytes(32).toString('hex'); + setSetting('cookie_secret', secret); + } + return secret; +} + +// Resolve the bcrypt password hash. The environment (PG_PASSWORD / PG_PASSWORD_HASH) +// is authoritative: when set, it overwrites the stored hash on every boot so that +// platform-managed password changes (e.g. the StartOS "Set Login Password" action, +// which restarts the service with a new PG_PASSWORD) actually take effect. +export function initPassword() { + if (config.passwordHash) { + setSetting('password_hash', config.passwordHash); + return config.passwordHash; + } + + const stored = getSetting('password_hash'); + + if (config.password) { + // Re-hash only when the password actually changed (bcrypt salts differ each run). + if (!stored || !bcrypt.compareSync(config.password, stored)) { + const hash = bcrypt.hashSync(config.password, 10); + setSetting('password_hash', hash); + // Password changed out from under existing sessions — invalidate them. + if (stored) db.prepare('DELETE FROM sessions').run(); + return hash; + } + return stored; + } + + if (stored) return stored; + + const hash = bcrypt.hashSync('gunner', 10); + setSetting('password_hash', hash); + console.warn('\n⚠ No PG_PASSWORD set — using default password "gunner". Set PG_PASSWORD before deploying.\n'); + return hash; +} + +export function verifyPassword(plain) { + const hash = config.passwordHash || getSetting('password_hash'); + if (!hash) return false; + return bcrypt.compareSync(String(plain || ''), hash); +} + +export function setPassword(plain) { + const hash = bcrypt.hashSync(String(plain), 10); + setSetting('password_hash', hash); + // Invalidate existing sessions when the password changes. + db.prepare('DELETE FROM sessions').run(); +} + +export function createSession() { + const token = randomBytes(32).toString('hex'); + const expires = new Date(Date.now() + config.sessionDays * 86400_000).toISOString(); + db.prepare('INSERT INTO sessions (token, expires_at) VALUES (?, ?)').run(token, expires); + return token; +} + +export function isValidSession(token) { + if (!token) return false; + const row = db.prepare('SELECT expires_at FROM sessions WHERE token = ?').get(token); + if (!row) return false; + if (new Date(row.expires_at).getTime() < Date.now()) { + db.prepare('DELETE FROM sessions WHERE token = ?').run(token); + return false; + } + return true; +} + +export function destroySession(token) { + if (token) db.prepare('DELETE FROM sessions WHERE token = ?').run(token); +} + +export function cleanupExpiredSessions() { + db.prepare("DELETE FROM sessions WHERE expires_at < datetime('now')").run(); +} diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..2e9c5cc --- /dev/null +++ b/src/config.js @@ -0,0 +1,25 @@ +import { mkdirSync } from 'node:fs'; +import { join, isAbsolute } from 'node:path'; + +const root = process.cwd(); + +function resolveDir(p, fallback) { + const dir = p || fallback; + return isAbsolute(dir) ? dir : join(root, dir); +} + +export const config = { + host: process.env.PG_HOST || '0.0.0.0', + port: Number(process.env.PG_PORT || 3000), + dataDir: resolveDir(process.env.PG_DATA_DIR, 'data'), + // Auth: prefer a pre-hashed value; otherwise hash PG_PASSWORD at boot. + // Defaults to "gunner" for local dev (a warning is logged). + passwordHash: process.env.PG_PASSWORD_HASH || '', + password: process.env.PG_PASSWORD || '', + cookieSecret: process.env.PG_COOKIE_SECRET || '', + sessionDays: Number(process.env.PG_SESSION_DAYS || 30), +}; + +mkdirSync(config.dataDir, { recursive: true }); + +export const dbPath = join(config.dataDir, 'premier-gunner.db'); diff --git a/src/db.js b/src/db.js new file mode 100644 index 0000000..94aedea --- /dev/null +++ b/src/db.js @@ -0,0 +1,26 @@ +import Database from 'better-sqlite3'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; +import { dbPath } from './config.js'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +export const db = new Database(dbPath); +db.pragma('journal_mode = WAL'); +db.pragma('foreign_keys = ON'); + +const schema = readFileSync(join(__dirname, 'schema.sql'), 'utf8'); +db.exec(schema); + +export function getSetting(key, fallback = null) { + const row = db.prepare('SELECT value FROM settings WHERE key = ?').get(key); + return row ? row.value : fallback; +} + +export function setSetting(key, value) { + db.prepare( + 'INSERT INTO settings (key, value) VALUES (?, ?) ' + + 'ON CONFLICT(key) DO UPDATE SET value = excluded.value' + ).run(key, String(value)); +} diff --git a/src/routes/auth.js b/src/routes/auth.js new file mode 100644 index 0000000..004a3e4 --- /dev/null +++ b/src/routes/auth.js @@ -0,0 +1,45 @@ +import { + COOKIE_NAME, verifyPassword, createSession, destroySession, setPassword, +} from '../auth.js'; +import { config } from '../config.js'; + +const cookieOpts = { + path: '/', + httpOnly: true, + sameSite: 'lax', + signed: true, + secure: process.env.NODE_ENV === 'production', + maxAge: config.sessionDays * 86400, +}; + +export default async function authRoutes(app) { + app.post('/api/login', async (req, reply) => { + const { password } = req.body || {}; + if (!verifyPassword(password)) { + return reply.code(401).send({ error: 'Wrong password' }); + } + const token = createSession(); + reply.setCookie(COOKIE_NAME, token, cookieOpts); + return { ok: true }; + }); + + app.post('/api/logout', async (req, reply) => { + const raw = req.cookies[COOKIE_NAME]; + const unsigned = raw ? reply.unsignCookie(raw) : null; + if (unsigned && unsigned.valid) destroySession(unsigned.value); + reply.clearCookie(COOKIE_NAME, { path: '/' }); + return { ok: true }; + }); + + app.get('/api/me', async () => ({ ok: true })); + + app.post('/api/password', async (req, reply) => { + const { current, next } = req.body || {}; + if (!verifyPassword(current)) return reply.code(401).send({ error: 'Wrong current password' }); + if (!next || String(next).length < 4) return reply.code(400).send({ error: 'New password too short' }); + setPassword(next); + const token = createSession(); + reply.setCookie(COOKIE_NAME, token, cookieOpts); + return { ok: true }; + }); +} diff --git a/src/routes/categories.js b/src/routes/categories.js new file mode 100644 index 0000000..b83105f --- /dev/null +++ b/src/routes/categories.js @@ -0,0 +1,77 @@ +import { db } from '../db.js'; + +function categoriesWithMetrics({ includeArchived = false } = {}) { + const cats = db.prepare( + `SELECT * FROM categories ${includeArchived ? '' : 'WHERE archived = 0'} ORDER BY sort_order, id` + ).all(); + const metrics = db.prepare('SELECT * FROM category_metrics ORDER BY sort_order, id').all(); + const byCat = new Map(); + for (const m of metrics) { + if (!byCat.has(m.category_id)) byCat.set(m.category_id, []); + byCat.get(m.category_id).push(m); + } + return cats.map((c) => ({ ...c, metrics: byCat.get(c.id) || [] })); +} + +export { categoriesWithMetrics }; + +export default async function categoryRoutes(app) { + app.get('/api/categories', async (req) => { + const includeArchived = req.query?.all === '1'; + return categoriesWithMetrics({ includeArchived }); + }); + + app.post('/api/categories', async (req, reply) => { + const { name, emoji, color, metrics } = req.body || {}; + if (!name) return reply.code(400).send({ error: 'Name required' }); + const maxOrder = db.prepare('SELECT COALESCE(MAX(sort_order), -1) AS m FROM categories').get().m; + const result = db.prepare( + 'INSERT INTO categories (name, emoji, color, sort_order) VALUES (?, ?, ?, ?)' + ).run(name, emoji || '⚽', color || '#EF0107', maxOrder + 1); + const catId = result.lastInsertRowid; + const list = Array.isArray(metrics) && metrics.length + ? metrics + : [{ name: 'Minutes', unit: 'min', kind: 'duration', step: 5 }]; + list.forEach((m, i) => { + db.prepare( + 'INSERT INTO category_metrics (category_id, name, unit, kind, step, higher_is_better, sort_order) ' + + 'VALUES (?, ?, ?, ?, ?, ?, ?)' + ).run(catId, m.name || 'Value', m.unit || '', m.kind || 'count', m.step || 1, m.higher_is_better ?? 1, i); + }); + return categoriesWithMetrics({ includeArchived: true }).find((c) => c.id === Number(catId)); + }); + + app.put('/api/categories/:id', async (req, reply) => { + const id = Number(req.params.id); + const cat = db.prepare('SELECT * FROM categories WHERE id = ?').get(id); + if (!cat) return reply.code(404).send({ error: 'Not found' }); + const { name, emoji, color, archived } = req.body || {}; + db.prepare( + 'UPDATE categories SET name = ?, emoji = ?, color = ?, archived = ? WHERE id = ?' + ).run(name ?? cat.name, emoji ?? cat.emoji, color ?? cat.color, + archived !== undefined ? (archived ? 1 : 0) : cat.archived, id); + return categoriesWithMetrics({ includeArchived: true }).find((c) => c.id === id); + }); + + // Add a metric to an existing category. + app.post('/api/categories/:id/metrics', async (req, reply) => { + const id = Number(req.params.id); + const cat = db.prepare('SELECT id FROM categories WHERE id = ?').get(id); + if (!cat) return reply.code(404).send({ error: 'Not found' }); + const { name, unit, kind, step, higher_is_better } = req.body || {}; + const maxOrder = db.prepare( + 'SELECT COALESCE(MAX(sort_order), -1) AS m FROM category_metrics WHERE category_id = ?' + ).get(id).m; + db.prepare( + 'INSERT INTO category_metrics (category_id, name, unit, kind, step, higher_is_better, sort_order) ' + + 'VALUES (?, ?, ?, ?, ?, ?, ?)' + ).run(id, name || 'Value', unit || '', kind || 'count', step || 1, higher_is_better ?? 1, maxOrder + 1); + return categoriesWithMetrics({ includeArchived: true }).find((c) => c.id === id); + }); + + app.delete('/api/metrics/:id', async (req, reply) => { + const id = Number(req.params.id); + db.prepare('DELETE FROM category_metrics WHERE id = ?').run(id); + return reply.send({ ok: true }); + }); +} diff --git a/src/routes/entries.js b/src/routes/entries.js new file mode 100644 index 0000000..f84270c --- /dev/null +++ b/src/routes/entries.js @@ -0,0 +1,82 @@ +import { db } from '../db.js'; + +const ISO = /^\d{4}-\d{2}-\d{2}$/; + +function ensureDay(day) { + db.prepare('INSERT OR IGNORE INTO training_days (day) VALUES (?)').run(day); +} + +function dayPayload(day) { + const td = db.prepare('SELECT day, notes FROM training_days WHERE day = ?').get(day) + || { day, notes: '' }; + const entries = db.prepare( + 'SELECT id, category_id, created_at FROM entries WHERE day = ? ORDER BY id' + ).all(day); + const valuesByEntry = new Map(); + if (entries.length) { + const ids = entries.map((e) => e.id); + const rows = db.prepare( + `SELECT entry_id, metric_id, value FROM entry_values WHERE entry_id IN (${ids.map(() => '?').join(',')})` + ).all(...ids); + for (const r of rows) { + if (!valuesByEntry.has(r.entry_id)) valuesByEntry.set(r.entry_id, []); + valuesByEntry.get(r.entry_id).push({ metric_id: r.metric_id, value: r.value }); + } + } + const plans = db.prepare( + 'SELECT category_id, note FROM plans WHERE day = ?' + ).all(day); + return { + day: td.day, + notes: td.notes, + entries: entries.map((e) => ({ ...e, values: valuesByEntry.get(e.id) || [] })), + plans, + }; +} + +export default async function entryRoutes(app) { + app.get('/api/day/:day', async (req, reply) => { + const { day } = req.params; + if (!ISO.test(day)) return reply.code(400).send({ error: 'Bad date' }); + return dayPayload(day); + }); + + app.post('/api/entries', async (req, reply) => { + const { day, category_id, values } = req.body || {}; + if (!ISO.test(day || '')) return reply.code(400).send({ error: 'Bad date' }); + const cat = db.prepare('SELECT id FROM categories WHERE id = ?').get(Number(category_id)); + if (!cat) return reply.code(400).send({ error: 'Unknown category' }); + + const tx = db.transaction(() => { + ensureDay(day); + const { lastInsertRowid: entryId } = db.prepare( + 'INSERT INTO entries (day, category_id) VALUES (?, ?)' + ).run(day, cat.id); + if (Array.isArray(values)) { + const ins = db.prepare('INSERT INTO entry_values (entry_id, metric_id, value) VALUES (?, ?, ?)'); + for (const v of values) { + if (v && v.metric_id != null) ins.run(entryId, Number(v.metric_id), Number(v.value) || 0); + } + } + return entryId; + }); + tx(); + return dayPayload(day); + }); + + app.delete('/api/entries/:id', async (req, reply) => { + const id = Number(req.params.id); + const row = db.prepare('SELECT day FROM entries WHERE id = ?').get(id); + db.prepare('DELETE FROM entries WHERE id = ?').run(id); + return reply.send(row ? dayPayload(row.day) : { ok: true }); + }); + + app.put('/api/day/:day/notes', async (req, reply) => { + const { day } = req.params; + if (!ISO.test(day)) return reply.code(400).send({ error: 'Bad date' }); + const notes = String((req.body && req.body.notes) || ''); + ensureDay(day); + db.prepare('UPDATE training_days SET notes = ? WHERE day = ?').run(notes, day); + return { ok: true, notes }; + }); +} diff --git a/src/routes/goals.js b/src/routes/goals.js new file mode 100644 index 0000000..10cadd3 --- /dev/null +++ b/src/routes/goals.js @@ -0,0 +1,48 @@ +import { db } from '../db.js'; + +const KINDS = new Set(['session_count', 'metric_best', 'metric_total']); + +export default async function goalRoutes(app) { + app.get('/api/goals', async () => db.prepare('SELECT * FROM goals ORDER BY is_main DESC, sort_order, id').all()); + + app.post('/api/goals', async (req, reply) => { + const b = req.body || {}; + const scope = b.scope === 'overall' ? 'overall' : 'category'; + const kind = KINDS.has(b.kind) ? b.kind : 'session_count'; + if (kind !== 'session_count' && !b.metric_id) { + return reply.code(400).send({ error: 'metric_id required for metric goals' }); + } + if (scope === 'category' && !b.category_id) { + return reply.code(400).send({ error: 'category_id required for category goals' }); + } + // Only one main goal at a time. + if (b.is_main) db.prepare('UPDATE goals SET is_main = 0').run(); + const maxOrder = db.prepare('SELECT COALESCE(MAX(sort_order), -1) AS m FROM goals').get().m; + const result = db.prepare( + 'INSERT INTO goals (scope, category_id, metric_id, kind, target, label, reward, is_main, sort_order) ' + + 'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)' + ).run(scope, b.category_id || null, b.metric_id || null, kind, Number(b.target) || 0, + String(b.label || ''), String(b.reward || ''), b.is_main ? 1 : 0, maxOrder + 1); + return db.prepare('SELECT * FROM goals WHERE id = ?').get(result.lastInsertRowid); + }); + + app.put('/api/goals/:id', async (req, reply) => { + const id = Number(req.params.id); + const g = db.prepare('SELECT * FROM goals WHERE id = ?').get(id); + if (!g) return reply.code(404).send({ error: 'Not found' }); + const b = req.body || {}; + if (b.is_main) db.prepare('UPDATE goals SET is_main = 0').run(); + db.prepare( + 'UPDATE goals SET target = ?, label = ?, reward = ?, is_main = ? WHERE id = ?' + ).run(b.target != null ? Number(b.target) : g.target, + b.label != null ? String(b.label) : g.label, + b.reward != null ? String(b.reward) : g.reward, + b.is_main != null ? (b.is_main ? 1 : 0) : g.is_main, id); + return db.prepare('SELECT * FROM goals WHERE id = ?').get(id); + }); + + app.delete('/api/goals/:id', async (req, reply) => { + db.prepare('DELETE FROM goals WHERE id = ?').run(Number(req.params.id)); + return reply.send({ ok: true }); + }); +} diff --git a/src/routes/plans.js b/src/routes/plans.js new file mode 100644 index 0000000..b2a551b --- /dev/null +++ b/src/routes/plans.js @@ -0,0 +1,34 @@ +import { db } from '../db.js'; + +const ISO = /^\d{4}-\d{2}-\d{2}$/; + +export default async function planRoutes(app) { + // Plans within a date range (inclusive). Defaults to next 14 days handled client-side. + app.get('/api/plans', async (req) => { + const { from, to } = req.query || {}; + if (ISO.test(from || '') && ISO.test(to || '')) { + return db.prepare( + 'SELECT id, day, category_id, note FROM plans WHERE day BETWEEN ? AND ? ORDER BY day, id' + ).all(from, to); + } + return db.prepare('SELECT id, day, category_id, note FROM plans ORDER BY day, id').all(); + }); + + app.post('/api/plans', async (req, reply) => { + const { day, category_id, note } = req.body || {}; + if (!ISO.test(day || '')) return reply.code(400).send({ error: 'Bad date' }); + const cat = db.prepare('SELECT id FROM categories WHERE id = ?').get(Number(category_id)); + if (!cat) return reply.code(400).send({ error: 'Unknown category' }); + db.prepare( + 'INSERT INTO plans (day, category_id, note) VALUES (?, ?, ?) ' + + 'ON CONFLICT(day, category_id) DO UPDATE SET note = excluded.note' + ).run(day, cat.id, String(note || '')); + return db.prepare('SELECT id, day, category_id, note FROM plans WHERE day = ? AND category_id = ?') + .get(day, cat.id); + }); + + app.delete('/api/plans/:id', async (req, reply) => { + db.prepare('DELETE FROM plans WHERE id = ?').run(Number(req.params.id)); + return reply.send({ ok: true }); + }); +} diff --git a/src/routes/stats.js b/src/routes/stats.js new file mode 100644 index 0000000..790b1a7 --- /dev/null +++ b/src/routes/stats.js @@ -0,0 +1,93 @@ +import { db } from '../db.js'; + +function todayISO() { + return new Date().toISOString().slice(0, 10); +} + +function goalProgress(goal) { + let current = 0; + if (goal.kind === 'session_count') { + current = goal.scope === 'overall' + ? db.prepare('SELECT COUNT(*) AS n FROM entries').get().n + : db.prepare('SELECT COUNT(*) AS n FROM entries WHERE category_id = ?').get(goal.category_id).n; + } else if (goal.kind === 'metric_best' && goal.metric_id) { + current = db.prepare('SELECT COALESCE(MAX(value), 0) AS v FROM entry_values WHERE metric_id = ?') + .get(goal.metric_id).v; + } else if (goal.kind === 'metric_total' && goal.metric_id) { + current = db.prepare('SELECT COALESCE(SUM(value), 0) AS v FROM entry_values WHERE metric_id = ?') + .get(goal.metric_id).v; + } + const pct = goal.target > 0 ? Math.min(100, Math.round((current / goal.target) * 100)) : 0; + return { ...goal, current, pct }; +} + +function streaks(days) { + // days: ISO strings sorted ascending, distinct + if (!days.length) return { current: 0, longest: 0 }; + const set = new Set(days); + let longest = 0; + for (const d of days) { + const prev = new Date(d); prev.setDate(prev.getDate() - 1); + if (!set.has(prev.toISOString().slice(0, 10))) { + // start of a run + let len = 0; const cur = new Date(d); + while (set.has(cur.toISOString().slice(0, 10))) { len++; cur.setDate(cur.getDate() + 1); } + if (len > longest) longest = len; + } + } + // current streak ending today or yesterday + let current = 0; const cur = new Date(todayISO()); + if (!set.has(cur.toISOString().slice(0, 10))) cur.setDate(cur.getDate() - 1); + while (set.has(cur.toISOString().slice(0, 10))) { current++; cur.setDate(cur.getDate() - 1); } + return { current, longest }; +} + +export default async function statsRoutes(app) { + app.get('/api/stats', async () => { + const totalSessions = db.prepare('SELECT COUNT(*) AS n FROM entries').get().n; + const dayRows = db.prepare('SELECT DISTINCT day FROM entries ORDER BY day').all().map((r) => r.day); + const totalDays = dayRows.length; + + // Heatmap: entries per day. + const heatmap = db.prepare( + 'SELECT day, COUNT(*) AS count FROM entries GROUP BY day ORDER BY day' + ).all(); + + // Radar: sessions per category (all-time + last 30 days). + const since = new Date(); since.setDate(since.getDate() - 30); + const since30 = since.toISOString().slice(0, 10); + const radar = db.prepare( + `SELECT c.id AS category_id, c.name, c.emoji, c.color, + COUNT(e.id) AS sessions, + SUM(CASE WHEN e.day >= ? THEN 1 ELSE 0 END) AS sessions30 + FROM categories c LEFT JOIN entries e ON e.category_id = c.id + WHERE c.archived = 0 + GROUP BY c.id ORDER BY c.sort_order, c.id` + ).all(since30); + + // Per-metric time series for line charts. + const seriesRows = db.prepare( + `SELECT ev.metric_id, e.day, ev.value, e.id AS entry_id + FROM entry_values ev JOIN entries e ON e.id = ev.entry_id + ORDER BY e.day, e.id` + ).all(); + const series = {}; + for (const r of seriesRows) { + (series[r.metric_id] ||= []).push({ day: r.day, value: r.value, entry_id: r.entry_id }); + } + + const goals = db.prepare('SELECT * FROM goals ORDER BY is_main DESC, sort_order, id').all() + .map(goalProgress); + + return { + totalSessions, + totalDays, + ...streaks(dayRows), + today: todayISO(), + heatmap, + radar, + series, + goals, + }; + }); +} diff --git a/src/schema.sql b/src/schema.sql new file mode 100644 index 0000000..01ba184 --- /dev/null +++ b/src/schema.sql @@ -0,0 +1,84 @@ +PRAGMA journal_mode = WAL; +PRAGMA foreign_keys = ON; + +CREATE TABLE IF NOT EXISTS categories ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + emoji TEXT NOT NULL DEFAULT '⚽', + color TEXT NOT NULL DEFAULT '#EF0107', + sort_order INTEGER NOT NULL DEFAULT 0, + archived INTEGER NOT NULL DEFAULT 0 +); + +-- What gets measured for a category (juggles, minutes, shots, etc.) +CREATE TABLE IF NOT EXISTS category_metrics ( + id INTEGER PRIMARY KEY, + category_id INTEGER NOT NULL REFERENCES categories(id) ON DELETE CASCADE, + name TEXT NOT NULL, + unit TEXT NOT NULL DEFAULT '', + kind TEXT NOT NULL DEFAULT 'count', -- count | duration | score + step INTEGER NOT NULL DEFAULT 1, + higher_is_better INTEGER NOT NULL DEFAULT 1, + sort_order INTEGER NOT NULL DEFAULT 0 +); + +-- One row per calendar day Gunner trains; holds the daily notes. +CREATE TABLE IF NOT EXISTS training_days ( + id INTEGER PRIMARY KEY, + day TEXT NOT NULL UNIQUE, -- ISO YYYY-MM-DD + notes TEXT NOT NULL DEFAULT '' +); + +-- A single logged practice of a category on a day. +CREATE TABLE IF NOT EXISTS entries ( + id INTEGER PRIMARY KEY, + day TEXT NOT NULL, + category_id INTEGER NOT NULL REFERENCES categories(id) ON DELETE CASCADE, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); +CREATE INDEX IF NOT EXISTS idx_entries_day ON entries(day); +CREATE INDEX IF NOT EXISTS idx_entries_cat ON entries(category_id); + +-- The metric readings captured for an entry. +CREATE TABLE IF NOT EXISTS entry_values ( + id INTEGER PRIMARY KEY, + entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE, + metric_id INTEGER NOT NULL REFERENCES category_metrics(id) ON DELETE CASCADE, + value REAL NOT NULL DEFAULT 0 +); +CREATE INDEX IF NOT EXISTS idx_entry_values_entry ON entry_values(entry_id); +CREATE INDEX IF NOT EXISTS idx_entry_values_metric ON entry_values(metric_id); + +-- Planned categories for a (usually future) day. +CREATE TABLE IF NOT EXISTS plans ( + id INTEGER PRIMARY KEY, + day TEXT NOT NULL, + category_id INTEGER NOT NULL REFERENCES categories(id) ON DELETE CASCADE, + note TEXT NOT NULL DEFAULT '', + UNIQUE(day, category_id) +); +CREATE INDEX IF NOT EXISTS idx_plans_day ON plans(day); + +CREATE TABLE IF NOT EXISTS goals ( + id INTEGER PRIMARY KEY, + scope TEXT NOT NULL DEFAULT 'category', -- overall | category + category_id INTEGER REFERENCES categories(id) ON DELETE CASCADE, + metric_id INTEGER REFERENCES category_metrics(id) ON DELETE CASCADE, + kind TEXT NOT NULL DEFAULT 'session_count', -- session_count | metric_best | metric_total + target REAL NOT NULL DEFAULT 0, + label TEXT NOT NULL DEFAULT '', + reward TEXT NOT NULL DEFAULT '', + is_main INTEGER NOT NULL DEFAULT 0, + sort_order INTEGER NOT NULL DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS sessions ( + token TEXT PRIMARY KEY, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + expires_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL +); diff --git a/src/seed.js b/src/seed.js new file mode 100644 index 0000000..6f0cc78 --- /dev/null +++ b/src/seed.js @@ -0,0 +1,74 @@ +import { db } from './db.js'; + +// Default categories Gunner starts with. He can edit/add/archive these later. +const DEFAULTS = [ + { name: 'Juggling', emoji: '🤹', color: '#EF0107', + metrics: [{ name: 'Juggles', unit: 'reps', kind: 'count', step: 5 }] }, + { name: 'Left Foot', emoji: '🦶', color: '#0a58ca', + metrics: [{ name: 'Minutes', unit: 'min', kind: 'duration', step: 5 }] }, + { name: 'Shooting', emoji: '🥅', color: '#d63384', + metrics: [ + { name: 'Shots', unit: 'shots', kind: 'count', step: 5 }, + { name: 'Goals', unit: 'goals', kind: 'count', step: 1 }, + ] }, + { name: 'Soccer Tennis', emoji: '🎾', color: '#198754', + metrics: [{ name: 'Minutes', unit: 'min', kind: 'duration', step: 5 }] }, + { name: 'Dribbling Drills', emoji: '🌀', color: '#fd7e14', + metrics: [{ name: 'Minutes', unit: 'min', kind: 'duration', step: 5 }] }, + { name: 'Soccer Golf', emoji: '⛳', color: '#6f42c1', + metrics: [{ name: 'Strokes', unit: 'strokes', kind: 'count', step: 1, higher_is_better: 0 }] }, + { name: 'Backyard with Dad', emoji: '🏡', color: '#20c997', + metrics: [{ name: 'Minutes', unit: 'min', kind: 'duration', step: 5 }] }, + { name: '1-on-1 with Elijah', emoji: '👟', color: '#0dcaf0', + metrics: [{ name: 'Minutes', unit: 'min', kind: 'duration', step: 5 }] }, + { name: 'EPA Agility & Speed', emoji: '⚡', color: '#ffc107', + metrics: [{ name: 'Minutes', unit: 'min', kind: 'duration', step: 5 }] }, +]; + +export function seedIfEmpty() { + const count = db.prepare('SELECT COUNT(*) AS n FROM categories').get().n; + if (count > 0) return false; + + const insCat = db.prepare( + 'INSERT INTO categories (name, emoji, color, sort_order) VALUES (?, ?, ?, ?)' + ); + const insMetric = db.prepare( + 'INSERT INTO category_metrics (category_id, name, unit, kind, step, higher_is_better, sort_order) ' + + 'VALUES (?, ?, ?, ?, ?, ?, ?)' + ); + + const seed = db.transaction(() => { + DEFAULTS.forEach((cat, ci) => { + const { lastInsertRowid: catId } = insCat.run(cat.name, cat.emoji, cat.color, ci); + cat.metrics.forEach((m, mi) => { + insMetric.run(catId, m.name, m.unit || '', m.kind || 'count', + m.step || 1, m.higher_is_better ?? 1, mi); + }); + }); + + // Main goal: the London / Arsenal reward, tracked as overall sessions. + db.prepare( + 'INSERT INTO goals (scope, kind, target, label, reward, is_main, sort_order) ' + + "VALUES ('overall', 'session_count', 100, ?, ?, 1, 0)" + ).run('Road to London', '✈️ Trip to London to see Arsenal play in person!'); + + // A starter juggling personal-best goal as an example. + const jug = db.prepare("SELECT id FROM categories WHERE name = 'Juggling'").get(); + const jugMetric = db.prepare( + 'SELECT id FROM category_metrics WHERE category_id = ? AND name = ?' + ).get(jug.id, 'Juggles'); + db.prepare( + 'INSERT INTO goals (scope, category_id, metric_id, kind, target, label, sort_order) ' + + "VALUES ('category', ?, ?, 'metric_best', 50, ?, 1)" + ).run(jug.id, jugMetric.id, 'Juggle 50 in a row'); + }); + + seed(); + return true; +} + +// Allow `npm run seed` to run standalone. +if (import.meta.url === `file://${process.argv[1]}`) { + const seeded = seedIfEmpty(); + console.log(seeded ? 'Seeded default categories and goals.' : 'Categories already exist — nothing to seed.'); +} diff --git a/src/server.js b/src/server.js new file mode 100644 index 0000000..31cb3e3 --- /dev/null +++ b/src/server.js @@ -0,0 +1,59 @@ +import Fastify from 'fastify'; +import fastifyStatic from '@fastify/static'; +import fastifyCookie from '@fastify/cookie'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +import { config } from './config.js'; +import { seedIfEmpty } from './seed.js'; +import { + COOKIE_NAME, getCookieSecret, initPassword, isValidSession, cleanupExpiredSessions, +} from './auth.js'; + +import authRoutes from './routes/auth.js'; +import categoryRoutes from './routes/categories.js'; +import entryRoutes from './routes/entries.js'; +import planRoutes from './routes/plans.js'; +import goalRoutes from './routes/goals.js'; +import statsRoutes from './routes/stats.js'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const publicDir = join(__dirname, '..', 'public'); + +// First-boot setup. +initPassword(); +seedIfEmpty(); +cleanupExpiredSessions(); + +const app = Fastify({ logger: { level: process.env.LOG_LEVEL || 'info' } }); + +await app.register(fastifyCookie, { secret: getCookieSecret() }); +await app.register(fastifyStatic, { root: publicDir, index: ['index.html'] }); + +// Gate every /api route except login. Static assets stay public; the data is what's protected. +const OPEN = new Set(['/api/login']); +app.addHook('preHandler', async (req, reply) => { + if (!req.url.startsWith('/api/')) return; + const path = req.url.split('?')[0]; + if (OPEN.has(path)) return; + const raw = req.cookies[COOKIE_NAME]; + const unsigned = raw ? reply.unsignCookie(raw) : null; + if (!unsigned || !unsigned.valid || !isValidSession(unsigned.value)) { + return reply.code(401).send({ error: 'Not authenticated' }); + } +}); + +await app.register(authRoutes); +await app.register(categoryRoutes); +await app.register(entryRoutes); +await app.register(planRoutes); +await app.register(goalRoutes); +await app.register(statsRoutes); + +try { + await app.listen({ host: config.host, port: config.port }); + console.log(`⚽ Premier Gunner running at http://${config.host}:${config.port}`); +} catch (err) { + app.log.error(err); + process.exit(1); +}