diff --git a/.github/workflows/manual-pages-deploy.yml b/.github/workflows/manual-pages-deploy.yml deleted file mode 100644 index c890067..0000000 --- a/.github/workflows/manual-pages-deploy.yml +++ /dev/null @@ -1,68 +0,0 @@ -# Workflow for building with Vite and deploying to GitHub Pages -name: Manual Build and Deploy to Pages - -on: - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - # Build job - build: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Required to fetch all tags for versioning - - - name: Get latest tag - id: vars - uses: actions-ecosystem/action-get-latest-tag@v1 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '22' - cache: 'npm' - - - name: Install dependencies - run: npm install - - - name: Build project with Vite - run: npm run build - - - name: Setup Pages - uses: actions/configure-pages@v5 - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: './pages' - - # Export the tag so the 'announce' job can use it - outputs: - latest_tag: ${{ steps.vars.outputs.tag }} - - # Deploy job - deploy: - needs: build - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/weekly-pages-deploy.yml b/.github/workflows/pages-deploy.yml similarity index 73% rename from .github/workflows/weekly-pages-deploy.yml rename to .github/workflows/pages-deploy.yml index 48c0a4d..7d88f97 100644 --- a/.github/workflows/weekly-pages-deploy.yml +++ b/.github/workflows/pages-deploy.yml @@ -1,11 +1,15 @@ # Workflow for building with Vite and deploying to GitHub Pages -name: Build with Vite and Deploy to Pages on Weekly Reset +name: Build with Vite and Deploy to Pages on: # Runs on weekly reset (UTC 00:00, Monday) #schedule: # - cron: '0 0 * * 1' + # Runs when you publish a release + release: + types: [published] + # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -23,38 +27,38 @@ concurrency: jobs: # Build job - build: + build: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '22' - cache: 'npm' + node-version: '24' + cache: 'npm' - name: Install dependencies - run: npm install + run: npm install - name: Build project with Vite - run: npm run build + run: npm run build - name: Check time (for debugging) run: date -u - name: Setup Pages (this action prepares for artifact upload) - uses: actions/configure-pages@v5 + uses: actions/configure-pages@v6 - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v5 with: - path: './pages' + path: './pages' # Deploy job deploy: - needs: build + needs: build environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} @@ -62,4 +66,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/release-assets.yml b/.github/workflows/release-assets.yml index 3130977..0b7b914 100644 --- a/.github/workflows/release-assets.yml +++ b/.github/workflows/release-assets.yml @@ -12,11 +12,16 @@ jobs: contents: write steps: - - name: Checkout code - uses: actions/checkout@v4 + - name: Checkout code (GitHub Actions) + if: ${{ !env.ACT }} + uses: actions/checkout@v6 with: ref: ${{ github.event_name == 'release' && github.event.release.tag_name || github.ref }} + - name: Checkout code (nektos/act local actions) + if: ${{ env.ACT }} + uses: actions/checkout@v6 + - name: Get Tag Name or Ref for Archives id: get_ref_name run: | @@ -37,9 +42,9 @@ jobs: echo "Using archive base name: Task-Checklist-${{ env.REF_FOR_ARCHIVE }}" - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '22' + node-version: '24' cache: 'npm' - name: Install dependencies @@ -60,8 +65,8 @@ jobs: run: | gh release upload "${{ github.event.release.tag_name }}" "./release/${{ steps.set_base_name.outputs.ARCHIVE_BASE_NAME }}.html" --clobber - - name: Upload Source Directory as Artifact (for manual/dispatch runs) - if: github.event_name == 'workflow_dispatch' + - name: Upload Release as Artifact (for manual/dispatch runs) + if: github.event_name == 'workflow_dispatch' || env.ACT uses: actions/upload-artifact@v4 with: name: ${{ steps.set_base_name.outputs.ARCHIVE_BASE_NAME }} diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..ed6d737 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,25 @@ +name: Tests + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: '24' + cache: 'npm' + + - name: Install dependencies + run: npm install + + - name: Run Tests + run: npm run test diff --git a/.gitignore b/.gitignore index ee070a9..1503da4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ /pages/ /release/ +/.vite/ +/.coverage/ + # Log files *.log npm-debug.log* diff --git a/README.md b/README.md index d2f6475..9c5cfe9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/warframe-tools/Task-Checklist) ![HTML5 Badge](https://img.shields.io/badge/HTML5-E34F26?logo=html5&logoColor=fff&style=flat) ![CSS3 Badge](https://img.shields.io/badge/CSS3-1572B6?logo=css3&logoColor=fff&style=flat) ![JavaScript Badge](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000&style=flat) +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/warframe-tools/Task-Checklist) ![HTML5 Badge](https://img.shields.io/badge/HTML5-E34F26?logo=html5&logoColor=fff&style=flat) ![CSS3 Badge](https://img.shields.io/badge/CSS3-1572B6?logo=css3&logoColor=fff&style=flat) ![JavaScript Badge](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000&style=flat) # Warframe Task Checklist Web App @@ -10,7 +10,7 @@ The hosted version of this application will be updated (if changes are made) on ## Description -This tool provides checklists for common recurring activities in Warframe. It saves your progress directly in your browser's local storage, automatically resets daily and weekly tasks based on UTC time, and offers a few customization options. The application is built using HTML, CSS (with Tailwind CSS via CDN for rapid styling), vanilla JavaScript, and is processed with Vite for an optimized production build. +This tool provides checklists for common recurring activities in Warframe. It saves your progress directly in your browser's local storage, automatically resets daily and weekly tasks based on UTC time, and offers a few customization options. The application is built using HTML, CSS, vanilla JavaScript, and is processed with Vite for an optimized production build. ## Fan Made Reviews/Showcase @@ -60,9 +60,8 @@ This tool provides checklists for common recurring activities in Warframe. It sa * Includes a "Copy" button in the error display for easy reporting. * **Performance Optimizations:** * **Vite Build Process:** The application is built using Vite, which optimizes and bundles JavaScript and CSS, including filename hashing for efficient cache-busting. - * **Critical CSS Inlining:** Critical CSS is inlined in the deployed `index.html` via `vite-plugin-html` to ensure the fastest possible initial paint. Non-critical CSS is loaded asynchronously. + * **Critical CSS Inlining:** Critical CSS is inlined in the deployed `index.html` to ensure the fastest possible initial paint. Non-critical CSS is loaded asynchronously. * **Self-Hosted Fonts:** The "Inter" font is self-hosted to reduce external dependencies and improve load times. -* **Responsive Design:** Styled with Tailwind CSS for a generally responsive layout. * **Footer Information:** Includes links to relevant resources (WarframeTools, GitHub, License, Warframe Hub, Warframe Wiki, FrameHub), app version, and current Warframe version. ## How to Use @@ -131,9 +130,11 @@ If you find this tool useful, consider supporting its development! ## Credits -* **Vite:** [https://vitejs.dev/](https://vitejs.dev/) -* **Tailwind CSS:** [https://tailwindcss.com/](https://tailwindcss.com/) -* **Inter Font:** Self-hosted.(https://fonts.google.com/specimen/Inter). +* **Vite:** [vite.dev](https://vite.dev/) +* **Inter Font:** Self-hosted. (https://fonts.google.com/specimen/Inter) +* **Heroicons:** [heroicons.com](https://heroicons.com/) +* [sanitize.css](https://www.npmjs.com/package/sanitize.css) +* [Vitest](https://vitest.dev/) and [Ajv](https://ajv.js.org/) for automated testing tools. * Task list based on information discussed in the Warframe community. * @AnSq for the full overhaul and major features. @@ -141,7 +142,7 @@ If you find this tool useful, consider supporting its development! This is an unofficial fan-made tool. Warframe and all related assets are the intellectual property of Digital Extremes Ltd. This project is not affiliated with, endorsed by, or sponsored by Digital Extremes Ltd. -> [!NOTE] +> [!NOTE] > The warframetools.com website uses [**Cloudflare Analytics**](https://www.cloudflare.com/web-analytics/) to view privacy friendly analytics information for visitors. You are free to use a tracker blocker, and it will not affect the tool from working. If you are not comfortable with this, please download and use it locally, or fork the repository and host it yourself on your own [Github Pages](https://pages.github.com/). ## License diff --git a/SECURITY.md b/SECURITY.md index 5f411dd..2f061e1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,7 +6,7 @@ This security policy applies to the latest version of the Warframe Task Checklis | Version | Supported | | ------- | ------------------ | -| 2.x.x | :white_check_mark: | +| 5.x.x | :white_check_mark: | ## Reporting a Vulnerability @@ -34,7 +34,7 @@ We are primarily interested in vulnerabilities such as: * **Cross-Site Scripting (XSS):** Vulnerabilities that could allow malicious scripts to run in another user's browser context (though the current application design minimizes this risk as it doesn't process external user-generated content). * **Data Exposure:** Issues related to the insecure handling or potential leakage of data stored in `localStorage` (though this data is local to the user's browser). -* **Dependency Issues:** Vulnerabilities originating from the external dependencies used (e.g., Tailwind CSS CDN - although these are generally managed by the CDN provider). +* **Dependency Issues:** Vulnerabilities originating from the external dependencies used. **Out of Scope:** diff --git a/package-lock.json b/package-lock.json index ddb1cc8..f539eca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,83 +9,400 @@ "version": "2.4.0", "license": "GPL-3.0-or-later", "devDependencies": { - "vite": "^6.4.2", - "vite-plugin-html": "^3.2.2", - "vite-plugin-singlefile": "^2.3.0" + "@vitest/coverage-v8": "^4.1.5", + "@vitest/ui": "^4.1.5", + "ajv": "^8.20.0", + "ajv-formats": "^3.0.1", + "jsdom": "^29.1.1", + "sanitize.css": "^13.0.0", + "vite": "^8.0.14", + "vite-plugin-singlefile": "^2.3.3", + "vitest": "^4.1.5" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", - "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", - "cpu": [ - "ppc64" - ], + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "aix" - ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", - "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", - "cpu": [ - "arm" - ], + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", - "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", - "cpu": [ - "arm64" + "node_modules/@csstools/css-calc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.0.tgz", + "integrity": "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.0.tgz", + "integrity": "sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", - "optional": true, - "os": [ - "android" + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.2.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", - "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", - "cpu": [ - "x64" + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz", + "integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.132.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", + "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", - "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", + "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", "cpu": [ "arm64" ], @@ -93,18 +410,18 @@ "license": "MIT", "optional": true, "os": [ - "darwin" + "android" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", - "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", @@ -113,30 +430,30 @@ "darwin" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", - "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", + "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "freebsd" + "darwin" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", - "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", + "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", "cpu": [ "x64" ], @@ -147,13 +464,13 @@ "freebsd" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", - "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", + "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", "cpu": [ "arm" ], @@ -164,149 +481,169 @@ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", - "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", + "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", - "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", + "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", "cpu": [ - "ia32" + "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", - "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", + "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", "cpu": [ - "loong64" + "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", - "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", + "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", "cpu": [ - "mips64el" + "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", - "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz", + "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==", "cpu": [ - "ppc64" + "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", - "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz", + "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==", "cpu": [ - "riscv64" + "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", - "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", + "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", "cpu": [ - "s390x" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "openharmony" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", - "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", + "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", "cpu": [ - "x64" + "wasm32" ], "dev": true, "license": "MIT", "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", - "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", + "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", "cpu": [ "arm64" ], @@ -314,16 +651,16 @@ "license": "MIT", "optional": true, "os": [ - "netbsd" + "win32" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", - "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", + "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", "cpu": [ "x64" ], @@ -331,1244 +668,960 @@ "license": "MIT", "optional": true, "os": [ - "netbsd" + "win32" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", - "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", - "cpu": [ - "arm64" - ], + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", - "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", - "cpu": [ - "x64" - ], + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", - "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", - "cpu": [ - "x64" - ], + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.5.tgz", + "integrity": "sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.5", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.5", + "vitest": "4.1.5" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", - "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", - "cpu": [ - "arm64" - ], + "node_modules/@vitest/expect": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", + "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", - "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", - "cpu": [ - "ia32" - ], + "node_modules/@vitest/mocker": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", + "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@vitest/spy": "4.1.5", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", - "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", - "cpu": [ - "x64" - ], + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@types/estree": "^1.0.0" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "node_modules/@vitest/pretty-format": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", + "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", "dev": true, - "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" + "tinyrainbow": "^3.1.0" }, - "engines": { - "node": ">=6.0.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@vitest/runner": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", + "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" + "dependencies": { + "@vitest/utils": "4.1.5", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "node_modules/@vitest/runner/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", + "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "@vitest/utils": "4.1.5", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, + "node_modules/@vitest/spy": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", + "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", + "dev": true, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "node_modules/@vitest/ui": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.5.tgz", + "integrity": "sha512-3Z9HNFiV0IF1fk0JPiK+7kE1GcaIPefQQIBYur6PM5yFIq6agys3uqP/0t966e1wXfmjbRCHDe7qW236Xjwnag==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "@vitest/utils": "4.1.5", + "fflate": "^0.8.2", + "flatted": "^3.4.2", + "pathe": "^2.0.3", + "sirv": "^3.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "4.1.5" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "node_modules/@vitest/ui/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "node_modules/@vitest/utils": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", + "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "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==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "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/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "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==", "dev": true, "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "ajv": "^8.0.0" }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, "engines": { - "node": ">= 8" + "node": ">=12" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", + "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 8" + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/ast-v8-to-istanbul/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" + "@types/estree": "^1.0.0" } }, - "node_modules/@rollup/pluginutils": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", - "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", "dependencies": { - "estree-walker": "^2.0.1", - "picomatch": "^2.2.2" + "fill-range": "^7.1.1" }, "engines": { - "node": ">= 8.0.0" + "node": ">=8" } }, - "node_modules/@rollup/pluginutils/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, - "license": "MIT", "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">=18" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", - "cpu": [ - "arm64" - ], + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", - "cpu": [ - "arm64" - ], + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "license": "MIT" }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", - "cpu": [ - "x64" - ], + "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==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" - ], + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true, - "license": "ISC" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" + "node": ">=12.0.0" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "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==", "dev": true, "license": "MIT" }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "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==", "dev": true, - "license": "MIT", - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/clean-css": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", - "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "source-map": "~0.6.0" + "node": ">=12.0.0" }, - "engines": { - "node": ">= 10.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" + "peerDependencies": { + "picomatch": "^3 || ^4" }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/consola": { - "version": "2.15.3", - "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", - "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", "dev": true, "license": "MIT" }, - "node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.2.0" + "to-regex-range": "^5.0.1" }, "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" + "node": ">=8" } }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } + "license": "ISC" }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/dotenv": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", - "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", - "dev": true, - "license": "BSD-2-Clause", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/dotenv-expand": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-8.0.3.tgz", - "integrity": "sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" + "@exodus/bytes": "^1.6.0" }, "engines": { - "node": ">=0.10.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, - "license": "BSD-2-Clause", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } + "license": "MIT" }, - "node_modules/esbuild": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", - "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.4", - "@esbuild/android-arm": "0.25.4", - "@esbuild/android-arm64": "0.25.4", - "@esbuild/android-x64": "0.25.4", - "@esbuild/darwin-arm64": "0.25.4", - "@esbuild/darwin-x64": "0.25.4", - "@esbuild/freebsd-arm64": "0.25.4", - "@esbuild/freebsd-x64": "0.25.4", - "@esbuild/linux-arm": "0.25.4", - "@esbuild/linux-arm64": "0.25.4", - "@esbuild/linux-ia32": "0.25.4", - "@esbuild/linux-loong64": "0.25.4", - "@esbuild/linux-mips64el": "0.25.4", - "@esbuild/linux-ppc64": "0.25.4", - "@esbuild/linux-riscv64": "0.25.4", - "@esbuild/linux-s390x": "0.25.4", - "@esbuild/linux-x64": "0.25.4", - "@esbuild/netbsd-arm64": "0.25.4", - "@esbuild/netbsd-x64": "0.25.4", - "@esbuild/openbsd-arm64": "0.25.4", - "@esbuild/openbsd-x64": "0.25.4", - "@esbuild/sunos-x64": "0.25.4", - "@esbuild/win32-arm64": "0.25.4", - "@esbuild/win32-ia32": "0.25.4", - "@esbuild/win32-x64": "0.25.4" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "node": ">=0.12.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true, "license": "MIT" }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=8.6.0" + "node": ">=10" } }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "reusify": "^1.0.4" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", + "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", "dev": true, "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, "peerDependencies": { - "picomatch": "^3 || ^4" + "canvas": "^3.0.0" }, "peerDependenciesMeta": { - "picomatch": { + "canvas": { "optional": true } } }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "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==", "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } + "license": "MIT" }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, - "license": "ISC", + "license": "MPL-2.0", "dependencies": { - "brace-expansion": "^2.0.1" + "detect-libc": "^2.0.3" }, "engines": { - "node": ">=10" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" + "node": ">= 12.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=8" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], "dev": true, - "hasInstallScript": true, - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "darwin" ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">= 6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], "dev": true, - "license": "ISC" + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=0.12.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "node_modules/lru-cache": { + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", + "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10" + "node": "20 || >=22" } }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, - "license": "MIT", "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.0.3" + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, "engines": { - "node": ">= 8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -1596,23 +1649,20 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">=10" } }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ { @@ -1628,70 +1678,42 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", "dev": true, - "license": "MIT", - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ] }, - "node_modules/node-html-parser": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-5.4.2.tgz", - "integrity": "sha512-RaBPP3+51hPne/OolXxcz89iYvQvKOydaqoePpOgXcrOKZhjVIzmpKZz+Hd/RBO2/zN2q6CNJhQzucVz+u3Jyw==", + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", "dev": true, "license": "MIT", "dependencies": { - "css-select": "^4.2.1", - "he": "1.2.0" + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "node_modules/parse5/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", "dev": true, "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" + "engines": { + "node": ">=20.19.0" }, "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/pathe": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-0.2.0.tgz", - "integrity": "sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==", - "dev": true, - "license": "MIT" - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1713,9 +1735,9 @@ } }, "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -1733,7 +1755,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -1741,125 +1763,112 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "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/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.10" + "node": ">=6" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "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==", "dev": true, "license": "MIT", "engines": { - "iojs": ">=1.0.0", "node": ">=0.10.0" } }, - "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "node_modules/rolldown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz", + "integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@oxc-project/types": "=0.132.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "@rolldown/binding-android-arm64": "1.0.2", + "@rolldown/binding-darwin-arm64": "1.0.2", + "@rolldown/binding-darwin-x64": "1.0.2", + "@rolldown/binding-freebsd-x64": "1.0.2", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.2", + "@rolldown/binding-linux-arm64-gnu": "1.0.2", + "@rolldown/binding-linux-arm64-musl": "1.0.2", + "@rolldown/binding-linux-ppc64-gnu": "1.0.2", + "@rolldown/binding-linux-s390x-gnu": "1.0.2", + "@rolldown/binding-linux-x64-gnu": "1.0.2", + "@rolldown/binding-linux-x64-musl": "1.0.2", + "@rolldown/binding-openharmony-arm64": "1.0.2", + "@rolldown/binding-wasm32-wasi": "1.0.2", + "@rolldown/binding-win32-arm64-msvc": "1.0.2", + "@rolldown/binding-win32-x64-msvc": "1.0.2" + } + }, + "node_modules/sanitize.css": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", + "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, - "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", + "license": "ISC", "dependencies": { - "queue-microtask": "^1.2.2" + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" } }, "node_modules/source-map-js": { @@ -1872,16 +1881,17 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true }, "node_modules/supports-color": { "version": "7.2.0", @@ -1896,41 +1906,36 @@ "node": ">=8" } }, - "node_modules/terser": { - "version": "5.39.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.2.tgz", - "integrity": "sha512-yEPUmWve+VA78bI71BW70Dh0TuV4HHd+I5SHOAfS1+QBOmvmCiiffgjR8ryyEd3KIfvPGFqoADt8LdQ6XpXIvg==", + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.14.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } + "license": "MIT" }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true + }, + "node_modules/tinyexec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", "dev": true, - "license": "MIT" + "engines": { + "node": ">=18" + } }, "node_modules/tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, - "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -1939,6 +1944,35 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.30", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.30.tgz", + "integrity": "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.30" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.30", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.30.tgz", + "integrity": "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1952,42 +1986,78 @@ "node": ">=8.0" } }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "0BSD" + "license": "0BSD", + "optional": true }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "node_modules/undici": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 10.0.0" + "node": ">=20.18.1" } }, "node_modules/vite": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", - "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "version": "8.0.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz", + "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.2", + "tinyglobby": "^0.2.16" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -1996,14 +2066,15 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -2012,13 +2083,16 @@ "@types/node": { "optional": true }, - "jiti": { + "@vitejs/devtools": { "optional": true }, - "less": { + "esbuild": { "optional": true }, - "lightningcss": { + "jiti": { + "optional": true + }, + "less": { "optional": true }, "sass": { @@ -2044,45 +2118,203 @@ } } }, - "node_modules/vite-plugin-html": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/vite-plugin-html/-/vite-plugin-html-3.2.2.tgz", - "integrity": "sha512-vb9C9kcdzcIo/Oc3CLZVS03dL5pDlOFuhGlZYDCJ840BhWl/0nGeZWf3Qy7NlOayscY4Cm/QRgULCQkEZige5Q==", + "node_modules/vite-plugin-singlefile": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/vite-plugin-singlefile/-/vite-plugin-singlefile-2.3.3.tgz", + "integrity": "sha512-XVnGH0QzbOa8fxRSsHdCarVN1BSBXNi7uLMQYlrGRN5apdHkk62XQWRJhVever0lnfuyBkwn+kvVChdm/OoOUg==", "dev": true, "license": "MIT", "dependencies": { - "@rollup/pluginutils": "^4.2.0", - "colorette": "^2.0.16", - "connect-history-api-fallback": "^1.6.0", - "consola": "^2.15.3", - "dotenv": "^16.0.0", - "dotenv-expand": "^8.0.2", - "ejs": "^3.1.6", - "fast-glob": "^3.2.11", - "fs-extra": "^10.0.1", - "html-minifier-terser": "^6.1.0", - "node-html-parser": "^5.3.3", - "pathe": "^0.2.0" + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">18.0.0" }, "peerDependencies": { - "vite": ">=2.0.0" + "rollup": "^4.59.0", + "vite": "^5.4.21 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } } }, - "node_modules/vite-plugin-singlefile": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/vite-plugin-singlefile/-/vite-plugin-singlefile-2.3.0.tgz", - "integrity": "sha512-DAcHzYypM0CasNLSz/WG0VdKOCxGHErfrjOoyIPiNxTPTGmO6rRD/te93n1YL/s+miXq66ipF1brMBikf99c6A==", + "node_modules/vitest": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", + "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", "dev": true, "dependencies": { - "micromatch": "^4.0.8" + "@vitest/expect": "4.1.5", + "@vitest/mocker": "4.1.5", + "@vitest/pretty-format": "4.1.5", + "@vitest/runner": "4.1.5", + "@vitest/snapshot": "4.1.5", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" }, "engines": { - "node": ">18.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "rollup": "^4.44.1", - "vite": "^5.4.11 || ^6.0.0 || ^7.0.0" + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.5", + "@vitest/browser-preview": "4.1.5", + "@vitest/browser-webdriverio": "4.1.5", + "@vitest/coverage-istanbul": "4.1.5", + "@vitest/coverage-v8": "4.1.5", + "@vitest/ui": "4.1.5", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" } } } diff --git a/package.json b/package.json index 013e08d..dbf6346 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,15 @@ { "name": "task-checklist", - "version": "2.4.0", - "description": "A Warframe Task Checklist to track daily, weekly, and other tasks. Built with HTML, CSS (Tailwind via CDN), and vanilla JavaScript, processed with Vite.", + "version": "5.0", + "description": "A Warframe Task Checklist to track daily, weekly, and other tasks. Built with HTML, CSS, and vanilla JavaScript, processed with Vite.", "type": "module", "scripts": { "dev": "vite", "build": "vite build", "build-release": "vite build --mode release", - "preview": "vite preview" + "preview": "vite preview", + "test": "vitest", + "test:ui": "vitest --ui --open=false --coverage" }, "repository": { "type": "git", @@ -27,8 +29,14 @@ }, "homepage": "https://warframetools.com/Task-Checklist/", "devDependencies": { - "vite": "^6.4.2", - "vite-plugin-html": "^3.2.2", - "vite-plugin-singlefile": "^2.3.0" + "@vitest/coverage-v8": "^4.1.5", + "@vitest/ui": "^4.1.5", + "ajv": "^8.20.0", + "ajv-formats": "^3.0.1", + "jsdom": "^29.1.1", + "sanitize.css": "^13.0.0", + "vite": "^8.0.14", + "vite-plugin-singlefile": "^2.3.3", + "vitest": "^4.1.5" } } diff --git a/sources/css/critical.css b/sources/css/critical.css index 94da801..8fa2260 100644 --- a/sources/css/critical.css +++ b/sources/css/critical.css @@ -37,18 +37,22 @@ --text-muted: #6b7280; --text-header: #d6b06e; --text-label: #d1d5db; - --text-label-checked: #9ca3af; + --text-label-checked: var(--text-secondary); --border-color: #4b5563; - --checkbox-border: #6b7280; + --checkbox-check-color: var(--text-primary); + --checkbox-lock-color: var(--text-primary); --checkbox-bg: rgba(255, 255, 255, 0.1); - --checkbox-checked-bg: #d6b06e; - --checkbox-checked-border: #6b7280; - --link-color: #d6b06e; - --link-hover-color: #fff; - --theme-toggle-border: #4b5563; + --checkbox-border: var(--text-muted); + --checkbox-checked-bg: var(--text-header); + --checkbox-checked-border: var(--text-muted); + --checkbox-locked-bg: hsla(17, 100%, 70%, 0.15); + --checkbox-locked-border: var(--text-muted); + --link-color: var(--text-header); + --link-hover-color: var(--text-primary); + --theme-toggle-border: var(--border-color); --theme-toggle-hover-bg: rgba(255, 255, 255, 0.1); - --theme-toggle-hover-border: #9ca3af; - --theme-toggle-color: #9ca3af; + --theme-toggle-hover-border: var(--text-secondary); + --theme-toggle-color: var(--text-secondary); --section-header-hover-bg: rgba(255, 255, 255, 0.05); --confirm-bg: #f59e0b; --confirm-hover-bg: #d97706; @@ -60,15 +64,16 @@ --error-button-hover-bg: rgba(255, 255, 255, 0.2); --menu-panel-bg: #272e3a; --menu-panel-border: #374151; - --menu-title-color: #ffffff; - --menu-close-btn-color: #9ca3af; - --menu-close-btn-hover-color: #f3f4f6; + --menu-title-color: var(--text-primary); + --menu-close-btn-color: var(--text-secondary); + --menu-close-btn-hover-color: var(--text-primary); --hide-section-btn-bg: var(--text-muted); --hide-section-btn-hover-bg: var(--text-secondary); --hide-section-btn-text: var(--text-primary); --baro-timer-color: var(--text-secondary); --notification-btn-color: var(--text-muted); --notification-btn-active-color: #4ade80; + --backdrop-filter: blur(1px); } body.light-mode { @@ -76,18 +81,21 @@ body.light-mode { --text-primary: #1f2937; --text-secondary: #4b5563; --text-muted: #6b7280; - --text-header: #d6b06e; + --text-header: #795d2d; --text-label: #374151; - --text-label-checked: #6b7280; + --text-label-checked: var(--text-secondary); --border-color: #e5e7eb; - --checkbox-border: #cbd5e1; + --checkbox-check-color: var(--text-primary); + --checkbox-lock-color: var(--text-primary); --checkbox-bg: rgba(0, 0, 0, 0.02); - --link-color: #d6b06e; - --link-hover-color: #1f2937; + --checkbox-border: #cbd5e1; + --checkbox-locked-bg: hsla(17, 100%, 30%, 0.15); + --link-color: var(--text-header); + --link-hover-color: var(--text-primary); --theme-toggle-border: #d1d5db; --theme-toggle-hover-bg: rgba(0, 0, 0, 0.05); - --theme-toggle-hover-border: #6b7280; - --theme-toggle-color: #6b7280; + --theme-toggle-hover-border: var(--text-muted); + --theme-toggle-color: var(--text-muted); --section-header-hover-bg: rgba(0, 0, 0, 0.03); --error-bg: #fee2e2; --error-border: #f87171; @@ -95,10 +103,10 @@ body.light-mode { --error-button-bg: rgba(0, 0, 0, 0.05); --error-button-hover-bg: rgba(0, 0, 0, 0.1); --menu-panel-bg: #f9fafb; - --menu-panel-border: #e5e7eb; - --menu-title-color: #111827; - --menu-close-btn-color: #6b7280; - --menu-close-btn-hover-color: #1f2937; + --menu-panel-border: var(--border-color); + --menu-title-color: var(--text-primary); + --menu-close-btn-color: var(--text-muted); + --menu-close-btn-hover-color: var(--text-primary); --hide-section-btn-bg: var(--text-muted); --hide-section-btn-hover-bg: var(--text-secondary); --hide-section-btn-text: var(--text-primary); @@ -107,6 +115,23 @@ body.light-mode { --notification-btn-active-color: #22c55e; } +:where(img, svg) { + display: block; +} + +:where(blockquote, dd, dl, figure, h1, h2, h3, h4, h5, h6, hr, p, pre, menu, ol, ul) { + margin: 0; +} + +:where(dialog) { + padding: 0; + position: fixed; +} + +:where(button, input, optgroup, select, textarea) { + padding: 0; +} + /* --- Base Body Styles --- */ body { font-family: 'Inter', sans-serif; @@ -117,8 +142,8 @@ body { min-height: 100vh; background-color: #111827; color: var(--text-primary); - transition: background-color 0.3s ease, color 0.3s ease; position: relative; + --backdrop-color: hsl(from var(--bg-content) h s calc(l / 1.5) / 60%); } body::before { content: ""; @@ -127,16 +152,36 @@ body::before { background-color: rgba(0, 0, 0, 0.6); z-index: -1; } +body.light-mode::before { + background-color: rgba(255, 255, 255, 0.6); +} /* --- Content Box Element Styling --- */ .checklist-content { background-color: var(--bg-content); color: var(--text-primary); - transition: background-color 0.3s ease, color 0.3s ease; position: relative; z-index: 1; + max-width: 48rem; + margin: 2rem auto; + padding: 2rem; + border-radius: 0.5rem; + box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), + 0 8px 10px -6px rgb(0 0 0 / 0.1); +} +#site-header { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} +.checklist-content h1 { + color: var(--text-header); + font-size: 1.875rem; + line-height: 2.25rem; + font-weight: 700; } -.checklist-content h1 { color: var(--text-header); } .checklist-content h2 { color: var(--text-header); cursor: pointer; @@ -145,7 +190,14 @@ body::before { align-items: center; padding: 0.5rem 0.25rem; border-radius: 0.25rem; - transition: background-color 0.15s ease-in-out; + font-size: 1.25rem; + line-height: 1.75rem; + font-weight: 600; + margin-bottom: 0.25rem; + border-bottom: 1px solid var(--border-color); +} +.checklist-content h2 > span:first-of-type { + flex-grow: 1; } .checklist-content h2 .reset-time-local { font-size: 0.75rem; @@ -158,7 +210,17 @@ body::before { margin-top: 0.5rem; margin-bottom: 0.5rem; } -.checklist-content .border-b { border-color: var(--border-color); } + +/* Theme switching animation */ +@media (prefers-reduced-motion: no-preference) { + body { transition: background-color 0.3s ease, color 0.3s ease; } + body::before { transition: background-color 0.3s ease; } + .checklist-content { transition: background-color 0.3s ease, color 0.3s ease; } +} + +#checklist-container > section { + margin-bottom: 2rem; +} /* --- Collapsible Section Basics --- */ .collapse-icon { @@ -171,23 +233,32 @@ body::before { .parent-task-header[aria-expanded="false"] .collapse-icon { transform: rotate(-90deg); } -.section-content, .subtask-list { +.section-content, .subtask-collapsible { + display: grid; + grid-template-rows: 1fr; overflow: hidden; - max-height: 3000px; opacity: 1; - visibility: visible; - transition: max-height 0.5s ease-in-out, opacity 0.3s ease-in-out 0.1s, visibility 0s linear 0s; padding-left: 0.5rem; } -.section-content.collapsed, .subtask-list.collapsed { - max-height: 0; - opacity: 0; - visibility: hidden; - transition: max-height 0.4s ease-in-out, opacity 0.2s ease-in-out, visibility 0s linear 0.4s; +@media (prefers-reduced-motion: no-preference) { + .section-content, .subtask-collapsible { + transition: grid-template-rows 0.5s cubic-bezier(.23,1,.32,1), opacity 0.5s ease-in-out; + } +} +.section-content.collapsed, .subtask-collapsible.collapsed { + opacity: 0.5; + grid-template-rows: 0fr; +} +.section-content > :first-child, .subtask-collapsible > :first-child { + min-height: 0; } /* --- Theme Toggle & Hamburger Button Basics --- */ -#theme-toggle-button, #hamburger-button { +#site-controls { + display: flex; + align-items: center; +} +#site-header button { font-weight: 600; padding: 0.375rem; border-radius: 0.375rem; @@ -198,8 +269,9 @@ body::before { cursor: pointer; line-height: 1.5; } -#theme-toggle-button { margin-right: 0.5rem; } -#theme-toggle-button svg, #hamburger-button svg { +#site-header button:not(:last-of-type) { margin-right: 0.5rem; } +#site-header button:not(:first-of-type) { margin-left: 0.5rem; } +#site-header button svg { width: 1.25rem; height: 1.25rem; fill: currentColor; @@ -207,41 +279,24 @@ body::before { body.light-mode #moon-icon { display: none; } body:not(.light-mode) #sun-icon { display: none; } -/* --- Hamburger Menu Overlay Basics --- */ -#slideout-menu-overlay { - position: fixed; - top: 0; left: 0; right: 0; bottom: 0; - background-color: rgba(0,0,0,0.75); - z-index: 999; - display: none; - align-items: center; - justify-content: center; - opacity: 0; - transition: opacity 0.3s ease-in-out; -} -#slideout-menu-overlay.open { - display: flex; - opacity: 1; -} -#menu-content-box { +/* --- Menu Overlay Basics --- */ +dialog { background-color: var(--menu-panel-bg); border: 1px solid var(--menu-panel-border); box-shadow: 0 8px 16px rgba(0,0,0,0.2); - padding: 1.5rem; border-radius: 0.5rem; +} +dialog > div { + padding: 1.5rem; +} +#options-menu { width: 90%; max-width: 300px; - transform: scale(0.95); - opacity: 0; - transition: transform 0.2s ease-out 0.1s, opacity 0.2s ease-out 0.1s; -} -#slideout-menu-overlay.open #menu-content-box { - transform: scale(1); - opacity: 1; } .menu-header { display: flex; justify-content: space-between; + column-gap: 2rem; align-items: center; margin-bottom: 1rem; padding-bottom: 0.75rem; @@ -252,7 +307,7 @@ body:not(.light-mode) #sun-icon { display: none; } font-weight: 600; color: var(--menu-title-color); } -#menu-close-button { +.menu-close-button { background: none; border: none; font-size: 1.75rem; @@ -260,6 +315,7 @@ body:not(.light-mode) #sun-icon { display: none; } color: var(--menu-close-btn-color); cursor: pointer; opacity: 0.7; + align-self: flex-start; } /* Error Display Basic Structure */ @@ -282,6 +338,15 @@ body:not(.light-mode) #sun-icon { display: none; } } +.task-info-expander { + --current-cycle-svg-size: 1.125rem; + --task-info-expander-svg-mr: 0.2em; +} + +.current-cycle svg { + width: var(--current-cycle-svg-size); + height: var(--current-cycle-svg-size); +} .info-line svg { width: 0.9375rem; height: 0.9375rem; diff --git a/sources/css/style.css b/sources/css/style.css index 81c94dc..4e9fddc 100644 --- a/sources/css/style.css +++ b/sources/css/style.css @@ -3,15 +3,11 @@ /* --- Background Image Styles --- */ /* Paths are relative to this style.css file (e.g., if style.css is in sources/css/ and images are in sources/img/) */ -#bg-image-0 { - background-image: url('../img/garuda-fortuna.webp'); -} -#bg-image-1 { - background-image: url('../img/gara-eidolon.webp'); -} -#bg-image-2 { - background-image: url('../img/heart-of-deimos-warframe.webp'); -} +#bg-image-0 { background-image: url('../img/garuda-fortuna.webp'); } +#bg-image-1 { background-image: url('../img/gara-eidolon.webp'); } +#bg-image-2 { background-image: url('../img/heart-of-deimos-warframe.webp'); } +#bg-image-3 { background-image: url('../img/duviri.webp'); } +#bg-image-4 { background-image: url('../img/perita.webp'); } /* Add more #bg-image-X rules if you have more images */ .daily-background-image { @@ -31,19 +27,40 @@ .checklist-content h2:hover { background-color: var(--section-header-hover-bg); } -.checklist-content label, .parent-task-header .task-text { color: var(--text-label); } -.checklist-content #last-saved-timestamp, -.checklist-content #storage-notice { +.checklist-content .task-description { color: var(--text-label); } + +#save-info { + margin-top: 1rem; + text-align: right; color: var(--text-muted); font-size: 0.75rem; +} +#save-info p { margin-top: 0.25rem; } -.checklist-content .italic { color: var(--text-secondary); } -.checklist-content .text-xs { color: var(--text-muted); } +#save-status { + color: rgb(22 163 74 / 1); + opacity: 0; + font-size: 0.875rem; + line-height: 1.25rem; + transition: opacity 0.5s ease-in-out; +} +.reminder-notice { + color: var(--text-secondary); + font-size: 0.875rem; + line-height: 1.25rem; + font-style: italic; + margin-top: 2rem; +} + +footer { + text-align: center; +} .checklist-content .footer-links { font-size: 0.75rem; color: var(--text-muted); + margin-top: 2rem; } .checklist-content .footer-links a { color: var(--link-color); @@ -59,29 +76,57 @@ margin: 0 0.1rem; } +#footer-info { + font-size: 0.75rem; + line-height: 1rem; + color: var(--text-muted); + margin-top: 1rem; +} +.warframe-version-text { + margin-top: 0.25rem; +} +.disclaimer-text { + margin-top: 0.5rem; + padding: 0 1rem; +} + +.section-content ul { + list-style: none; + margin-top: 0.5rem; + padding: 0; +} + a.git-hash-text { text-decoration: underline; + color: inherit; } /* --- Collapsible Section Styles (Non-Critical Parts) --- */ -.subtask-list { - margin-left: 1.5rem; - padding-left: 0; - list-style: none; +.section-content .subtask-list { + margin-left: 2rem; + margin-top: 0.25rem; } .parent-task-header { display: flex; - align-items: center; + align-items: start; cursor: pointer; + margin-bottom: 0.25rem; } -.parent-task-header .task-text { - flex-grow: 1; +.task-description { + flex: 1 1 0%; margin-left: 0.5rem; + cursor: pointer; } -.task-text.checked, label.checked { +.task-description.checked { text-decoration: line-through; color: var(--text-label-checked); } +.task-description.unavailable { + cursor: not-allowed; +} +.task-description.unavailable .task-text { + font-style: italic; +} .task-item.hidden-task { display: none !important; } @@ -128,10 +173,11 @@ a.git-hash-text { .hide-section-button.visible { display: inline-block; } -.baro-countdown, .eight-hour-countdown { +.other-countdown { font-size: 0.75rem; color: var(--baro-timer-color); - margin-left: 0.25rem; + margin-left: 0.4rem; + display: inline-block; } @@ -148,14 +194,29 @@ input[type="checkbox"] { background-color: var(--checkbox-bg); flex-shrink: 0; } +input[type="checkbox"]::after { + position: absolute; + font-size: 0.8rem; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} input[type="checkbox"]:checked { background-color: var(--checkbox-checked-bg); border-color: var(--checkbox-checked-border); } input[type="checkbox"]:checked::after { - content: '✔'; position: absolute; - color: white; font-size: 0.8rem; - top: 50%; left: 50%; transform: translate(-50%, -50%); + content: "✔"; + color: var(--checkbox-check-color); +} +input[type="checkbox"]:indeterminate { + background-color: var(--checkbox-locked-bg); + border-color: var(--checkbox-locked-border); + cursor: not-allowed; +} +input[type="checkbox"]:indeterminate::after { + content: "\1F512\FE0E"; /* LOCK + VARIATION SELECTOR-15 (text presentation) */ + color: var(--checkbox-lock-color); } li.task-item { display: flex; @@ -166,8 +227,8 @@ li.parent-task-container { display: flex; flex-direction: column; align-items: stretch; + margin-bottom: 0; } -#save-status { transition: opacity 0.5s ease-in-out; } /* --- Buttons (Base styles, colors set below) --- */ .menu-btn { @@ -230,11 +291,11 @@ li.parent-task-container { #error-close-button:hover { opacity: 1; } /* Hamburger Menu (Non-critical hover states) */ -#menu-close-button:hover { +.menu-close-button:hover { opacity: 1; color: var(--menu-close-btn-hover-color); } -#slideout-menu-overlay .menu-btn { +#options-menu .menu-btn { display: block; width: 100%; text-align: left; @@ -242,28 +303,203 @@ li.parent-task-container { margin-right: 0; margin-bottom: 0.75rem; } -#slideout-menu-overlay .menu-btn:last-child { +#options-menu .menu-btn:last-child { margin-bottom: 0; } +/* Dialogs */ +dialog { + color: var(--text-label); + max-width: 52rem; +} +dialog::backdrop { + background-color: var(--backdrop-color); + backdrop-filter: var(--backdrop-filter); +} + +@media (prefers-reduced-motion: no-preference) { + dialog { + --dialog-anim-duration: 0.15s; + --dialog-anim-easing: ease-out; + transition: transform var(--dialog-anim-duration) cubic-bezier(.25,1,.5,1), + opacity var(--dialog-anim-duration) var(--dialog-anim-easing), + display var(--dialog-anim-duration) allow-discrete, + overlay var(--dialog-anim-duration) allow-discrete; + transform: scale(1); + opacity: 1; + @starting-style { /* this section is necessary for the opening animation */ + transform: scale(0.95); + opacity: 0; + } + } + dialog:not([open]) { /* this section is necessary for the closing animation (closing animation doesn't work in Firefox) */ + transform: scale(0.95); + opacity: 0; + } + + dialog::backdrop { + transition: background-color var(--dialog-anim-duration) var(--dialog-anim-easing), + backdrop-filter var(--dialog-anim-duration) var(--dialog-anim-easing); + @starting-style { /* opening */ + background-color: transparent; + backdrop-filter: none; + } + } + dialog:not([open])::backdrop { /* closing */ + background-color: transparent; + backdrop-filter: none; + } +} + + /* Task Icons */ -.task-item img, .parent-task-header img { +.task-icon { height: 1.5em; width: 1.5em; } -body.light-mode .task-item img.icon-filter, body.light-mode .parent-task-header img.icon-filter { +body.light-mode img.icon-filter { filter: brightness(0) invert(0); } -body:not(.light-mode) .task-item img.icon-filter, body:not(.light-mode) .parent-task-header img.icon-filter { +body:not(.light-mode) img.icon-filter { filter: brightness(0) invert(1); } +.menu-title .task-icon { + /* height: 1em; + width: 1em; */ + display: inline; + margin-right: 0.35em; + vertical-align: bottom; +} +.menu-title .task-icon[src=""] { + display: none; + margin: 0; +} + +/* Collapsible Extra Task Info */ +.task-info-expander { + display: grid; + grid-template-rows: 1fr; + overflow: hidden; +} +@media (prefers-reduced-motion: no-preference) { + .task-info-expander { transition: grid-template-rows 200ms; /* https://css-tricks.com/css-grid-can-do-auto-height-transitions/ */ } +} +.task-item .checked .task-info-expander { + grid-template-rows: 0fr; +} +.task-info-expander > div { + min-height: 0; +} + +/* Cycles */ +.current-cycle { + font-size: 90%; + color: color-mix(in hsl, var(--text-label) 50%, var(--text-secondary) 50%); + margin-left: 0.5em; + text-indent: calc(var(--current-cycle-svg-size) + var(--task-info-expander-svg-mr)) hanging; +} +.cycle-icon { + height: 1.25em; + width: 1.25em; + display: inline; + vertical-align: text-bottom; + margin: 0 0.25em; +} +.current-cycle span:last-of-type { + margin-right: 1em; +} + +/* Cycle Schedule */ +.more-info-btn { + display: inline-block; + padding: 0 0.375em; + border-radius: 0.375rem; + transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease; + border: 1px solid var(--theme-toggle-border); + background-color: transparent; + font-size: 75%; + vertical-align: text-bottom; +} +.more-info-btn:hover { + background-color: var(--theme-toggle-hover-bg); + border-color: var(--theme-toggle-hover-border); + color: var(--text-label); +} + +#cycle-schedule, #more-info { + table { + margin: auto; + } + thead { + font-weight: 700; + font-size: 1.0625rem; + text-decoration: underline var(--menu-panel-border) 1px; + text-underline-offset: 0.25em; + } + th::before, th::after { + content: "\00a0\00a0\00a0\00a0"; /* some no-break spaces added so the underline extends a bit on both sides */ + } + :is(th, td):not(:last-child) { + padding-right: 1em; + } + td { + vertical-align: top; + } + th, td:not(:has(.cycle-icon)) { + text-align: center; + } + td:has(.cycle-icon) { + text-align: left; + } +} + +#cycle-schedule { + tbody tr:first-child { + font-weight: 600; + } + .cycle-repeats { + font-size: 75%; + color: var(--text-muted); + text-align: center; + } +} + +/* More Info */ +#more-info { + p:not(:last-child):not(:has(+ :is(ul, table))), table { + margin-bottom: 1em; + } + ul { + list-style: "\2212 " outside; + padding-left: 2em; + } + th, td { + text-align: center; + } + a { + color: var(--link-color); + text-decoration: underline; + transition: color 0.2s ease; + } + a:hover { + color: var(--link-hover-color); + } +} + +.inline-icon { + height: 1em; + width: 1em; + display: inline; + vertical-align: -10%; + margin: 0 0.125em; +} + /* Info Line */ .info-line { font-size: 75%; color: var(--text-secondary); margin-left: 0.5em; - min-height: 0; } .info-line > span:not(:last-of-type)::after { content: "|"; @@ -272,8 +508,8 @@ body:not(.light-mode) .task-item img.icon-filter, body:not(.light-mode) .parent- .info-line > span { white-space: nowrap; } -.info-line svg { - margin-right: 0.2em; +.task-info-expander svg { + margin-right: var(--task-info-expander-svg-mr); display: inline; vertical-align: text-bottom; } @@ -283,12 +519,3 @@ body:not(.light-mode) .task-item img.icon-filter, body:not(.light-mode) .parent- .tooltip { text-decoration: underline dotted; } -.info-line-expander { - display: grid; - grid-template-rows: 1fr; - overflow: hidden; - transition: grid-template-rows 100ms; /* https://css-tricks.com/css-grid-can-do-auto-height-transitions/ */ -} -.task-item .checked .info-line-expander { - grid-template-rows: 0fr; -} diff --git a/sources/img/duviri.webp b/sources/img/duviri.webp new file mode 100644 index 0000000..4a21b66 Binary files /dev/null and b/sources/img/duviri.webp differ diff --git a/sources/img/gara-eidolon.webp b/sources/img/gara-eidolon.webp index 1869f20..acd2120 100644 Binary files a/sources/img/gara-eidolon.webp and b/sources/img/gara-eidolon.webp differ diff --git a/sources/img/icons/Acrithis.png b/sources/img/icons/Acrithis.png deleted file mode 100644 index 5534e5b..0000000 Binary files a/sources/img/icons/Acrithis.png and /dev/null differ diff --git a/sources/img/icons/Bird3.png b/sources/img/icons/Bird3.png deleted file mode 100644 index 10e647b..0000000 Binary files a/sources/img/icons/Bird3.png and /dev/null differ diff --git a/sources/img/icons/Computer.png b/sources/img/icons/Computer.png deleted file mode 100644 index 8055580..0000000 Binary files a/sources/img/icons/Computer.png and /dev/null differ diff --git a/sources/img/icons/FocusLensFocus.png b/sources/img/icons/FocusLensFocus.png deleted file mode 100644 index d2848b3..0000000 Binary files a/sources/img/icons/FocusLensFocus.png and /dev/null differ diff --git a/sources/img/icons/Heat_d.png b/sources/img/icons/Heat_d.png deleted file mode 100644 index ff9b17d..0000000 Binary files a/sources/img/icons/Heat_d.png and /dev/null differ diff --git a/sources/img/icons/HelpClem_.png b/sources/img/icons/HelpClem_.png deleted file mode 100644 index 8899996..0000000 Binary files a/sources/img/icons/HelpClem_.png and /dev/null differ diff --git a/sources/img/icons/IconDuviriCategory256.png b/sources/img/icons/IconDuviriCategory256.png deleted file mode 100644 index 551b0fc..0000000 Binary files a/sources/img/icons/IconDuviriCategory256.png and /dev/null differ diff --git a/sources/img/icons/IconHelminth.png b/sources/img/icons/IconHelminth.png deleted file mode 100644 index 963a72f..0000000 Binary files a/sources/img/icons/IconHelminth.png and /dev/null differ diff --git a/sources/img/icons/IconInfested.png b/sources/img/icons/IconInfested.png deleted file mode 100644 index bf8b4ec..0000000 Binary files a/sources/img/icons/IconInfested.png and /dev/null differ diff --git a/sources/img/icons/IconNarmer.png b/sources/img/icons/IconNarmer.png deleted file mode 100644 index 549c487..0000000 Binary files a/sources/img/icons/IconNarmer.png and /dev/null differ diff --git a/sources/img/icons/IconOmegaMod256.png b/sources/img/icons/IconOmegaMod256.png deleted file mode 100644 index 56f8d61..0000000 Binary files a/sources/img/icons/IconOmegaMod256.png and /dev/null differ diff --git a/sources/img/icons/Kaya_.png b/sources/img/icons/Kaya_.png deleted file mode 100644 index bb33109..0000000 Binary files a/sources/img/icons/Kaya_.png and /dev/null differ diff --git a/sources/img/icons/LotusFlower.png b/sources/img/icons/LotusFlower.png deleted file mode 100644 index d85af5a..0000000 Binary files a/sources/img/icons/LotusFlower.png and /dev/null differ diff --git a/sources/img/icons/MagnifyingGlassIcon.png b/sources/img/icons/MagnifyingGlassIcon.png deleted file mode 100644 index c641c11..0000000 Binary files a/sources/img/icons/MagnifyingGlassIcon.png and /dev/null differ diff --git a/sources/img/icons/Market.png b/sources/img/icons/Market.png deleted file mode 100644 index 511b395..0000000 Binary files a/sources/img/icons/Market.png and /dev/null differ diff --git a/sources/img/icons/Maroo.png b/sources/img/icons/Maroo.png deleted file mode 100644 index 722edd2..0000000 Binary files a/sources/img/icons/Maroo.png and /dev/null differ diff --git a/sources/img/icons/MiniMapCaviaVendor_.png b/sources/img/icons/MiniMapCaviaVendor_.png deleted file mode 100644 index c7e3c97..0000000 Binary files a/sources/img/icons/MiniMapCaviaVendor_.png and /dev/null differ diff --git a/sources/img/icons/MysteryShroom256_d.png b/sources/img/icons/MysteryShroom256_d.png deleted file mode 100644 index 5e96f3b..0000000 Binary files a/sources/img/icons/MysteryShroom256_d.png and /dev/null differ diff --git a/sources/img/icons/Necraloid.png b/sources/img/icons/Necraloid.png deleted file mode 100644 index e47e0f1..0000000 Binary files a/sources/img/icons/Necraloid.png and /dev/null differ diff --git a/sources/img/icons/NetraRequiemIcon.png b/sources/img/icons/NetraRequiemIcon.png deleted file mode 100644 index 80ea2a4..0000000 Binary files a/sources/img/icons/NetraRequiemIcon.png and /dev/null differ diff --git a/sources/img/icons/NightwaveIconSimple.png b/sources/img/icons/NightwaveIconSimple.png deleted file mode 100644 index 8a06c56..0000000 Binary files a/sources/img/icons/NightwaveIconSimple.png and /dev/null differ diff --git a/sources/img/icons/Sortie.png b/sources/img/icons/Sortie.png deleted file mode 100644 index f103d10..0000000 Binary files a/sources/img/icons/Sortie.png and /dev/null differ diff --git a/sources/img/icons/SteelEssenceIcon.png b/sources/img/icons/SteelEssenceIcon.png deleted file mode 100644 index 12e06aa..0000000 Binary files a/sources/img/icons/SteelEssenceIcon.png and /dev/null differ diff --git a/sources/img/icons/Wisp.png b/sources/img/icons/Wisp.png deleted file mode 100644 index f76a451..0000000 Binary files a/sources/img/icons/Wisp.png and /dev/null differ diff --git a/sources/img/icons/Yonta.png b/sources/img/icons/Yonta.png deleted file mode 100644 index 9d3848f..0000000 Binary files a/sources/img/icons/Yonta.png and /dev/null differ diff --git a/sources/img/icons/cycles/ArchonShardAmar.png b/sources/img/icons/cycles/ArchonShardAmar.png new file mode 100644 index 0000000..0cecdc6 Binary files /dev/null and b/sources/img/icons/cycles/ArchonShardAmar.png differ diff --git a/sources/img/icons/cycles/ArchonShardBoreal.png b/sources/img/icons/cycles/ArchonShardBoreal.png new file mode 100644 index 0000000..73c492b Binary files /dev/null and b/sources/img/icons/cycles/ArchonShardBoreal.png differ diff --git a/sources/img/icons/cycles/ArchonShardNira.png b/sources/img/icons/cycles/ArchonShardNira.png new file mode 100644 index 0000000..d5172a8 Binary files /dev/null and b/sources/img/icons/cycles/ArchonShardNira.png differ diff --git a/sources/img/icons/cycles/EndoIconRenderLarge.png b/sources/img/icons/cycles/EndoIconRenderLarge.png new file mode 100644 index 0000000..92c9969 Binary files /dev/null and b/sources/img/icons/cycles/EndoIconRenderLarge.png differ diff --git a/sources/img/icons/cycles/FactionAmalgam.png b/sources/img/icons/cycles/FactionAmalgam.png new file mode 100644 index 0000000..30a3340 Binary files /dev/null and b/sources/img/icons/cycles/FactionAmalgam.png differ diff --git a/sources/img/icons/cycles/FactionCorpus.png b/sources/img/icons/cycles/FactionCorpus.png new file mode 100644 index 0000000..3688c79 Binary files /dev/null and b/sources/img/icons/cycles/FactionCorpus.png differ diff --git a/sources/img/icons/cycles/FactionGrineer.png b/sources/img/icons/cycles/FactionGrineer.png new file mode 100644 index 0000000..b0c75ae Binary files /dev/null and b/sources/img/icons/cycles/FactionGrineer.png differ diff --git a/sources/img/icons/cycles/FactionInfested.png b/sources/img/icons/cycles/FactionInfested.png new file mode 100644 index 0000000..fed3fa0 Binary files /dev/null and b/sources/img/icons/cycles/FactionInfested.png differ diff --git a/sources/img/icons/cycles/FactionOrokin.png b/sources/img/icons/cycles/FactionOrokin.png new file mode 100644 index 0000000..3beaa9e Binary files /dev/null and b/sources/img/icons/cycles/FactionOrokin.png differ diff --git a/sources/img/icons/cycles/FormaPack.png b/sources/img/icons/cycles/FormaPack.png new file mode 100644 index 0000000..629995a Binary files /dev/null and b/sources/img/icons/cycles/FormaPack.png differ diff --git a/sources/img/icons/cycles/FormaUmbra.png b/sources/img/icons/cycles/FormaUmbra.png new file mode 100644 index 0000000..d3baf15 Binary files /dev/null and b/sources/img/icons/cycles/FormaUmbra.png differ diff --git a/sources/img/icons/cycles/IconWarframe_256.png b/sources/img/icons/cycles/IconWarframe_256.png new file mode 100644 index 0000000..43f2bc4 Binary files /dev/null and b/sources/img/icons/cycles/IconWarframe_256.png differ diff --git a/sources/img/icons/cycles/IncarnonEvolveIconSmall.png b/sources/img/icons/cycles/IncarnonEvolveIconSmall.png new file mode 100644 index 0000000..f0e79e4 Binary files /dev/null and b/sources/img/icons/cycles/IncarnonEvolveIconSmall.png differ diff --git a/sources/img/icons/cycles/Kuva.png b/sources/img/icons/cycles/Kuva.png new file mode 100644 index 0000000..d4697ce Binary files /dev/null and b/sources/img/icons/cycles/Kuva.png differ diff --git a/sources/img/icons/cycles/OmegaModIconRendered256.png b/sources/img/icons/cycles/OmegaModIconRendered256.png new file mode 100644 index 0000000..9405b6b Binary files /dev/null and b/sources/img/icons/cycles/OmegaModIconRendered256.png differ diff --git a/sources/img/icons/cycles/OroFusexA.png b/sources/img/icons/cycles/OroFusexA.png new file mode 100644 index 0000000..e12f861 Binary files /dev/null and b/sources/img/icons/cycles/OroFusexA.png differ diff --git a/sources/img/icons/cycles/OroFusexB.png b/sources/img/icons/cycles/OroFusexB.png new file mode 100644 index 0000000..559e545 Binary files /dev/null and b/sources/img/icons/cycles/OroFusexB.png differ diff --git a/sources/img/icons/cycles/OroFusexC.png b/sources/img/icons/cycles/OroFusexC.png new file mode 100644 index 0000000..141ebaa Binary files /dev/null and b/sources/img/icons/cycles/OroFusexC.png differ diff --git a/sources/img/icons/cycles/OroFusexD.png b/sources/img/icons/cycles/OroFusexD.png new file mode 100644 index 0000000..05cbe10 Binary files /dev/null and b/sources/img/icons/cycles/OroFusexD.png differ diff --git a/sources/img/icons/cycles/OroFusexE.png b/sources/img/icons/cycles/OroFusexE.png new file mode 100644 index 0000000..9ab5d36 Binary files /dev/null and b/sources/img/icons/cycles/OroFusexE.png differ diff --git a/sources/img/icons/cycles/OroFusexG.png b/sources/img/icons/cycles/OroFusexG.png new file mode 100644 index 0000000..9f4ec9a Binary files /dev/null and b/sources/img/icons/cycles/OroFusexG.png differ diff --git a/sources/img/icons/cycles/RetroSeasonalAutumnIcon_.png b/sources/img/icons/cycles/RetroSeasonalAutumnIcon_.png new file mode 100644 index 0000000..814e88c Binary files /dev/null and b/sources/img/icons/cycles/RetroSeasonalAutumnIcon_.png differ diff --git a/sources/img/icons/cycles/RetroSeasonalSpringIcon_.png b/sources/img/icons/cycles/RetroSeasonalSpringIcon_.png new file mode 100644 index 0000000..abcde57 Binary files /dev/null and b/sources/img/icons/cycles/RetroSeasonalSpringIcon_.png differ diff --git a/sources/img/icons/cycles/RetroSeasonalSummerIcon_.png b/sources/img/icons/cycles/RetroSeasonalSummerIcon_.png new file mode 100644 index 0000000..7d42cd2 Binary files /dev/null and b/sources/img/icons/cycles/RetroSeasonalSummerIcon_.png differ diff --git a/sources/img/icons/cycles/RetroSeasonalWinterIcon_.png b/sources/img/icons/cycles/RetroSeasonalWinterIcon_.png new file mode 100644 index 0000000..a7394d7 Binary files /dev/null and b/sources/img/icons/cycles/RetroSeasonalWinterIcon_.png differ diff --git a/sources/img/icons/cycles/SentientSymbol.png b/sources/img/icons/cycles/SentientSymbol.png new file mode 100644 index 0000000..a816c01 Binary files /dev/null and b/sources/img/icons/cycles/SentientSymbol.png differ diff --git a/sources/img/icons/cycles/SteelEssence.png b/sources/img/icons/cycles/SteelEssence.png new file mode 100644 index 0000000..67d6f12 Binary files /dev/null and b/sources/img/icons/cycles/SteelEssence.png differ diff --git a/sources/img/icons/syndicates/FactionSigilCavia.png b/sources/img/icons/syndicates/FactionSigilCavia.png deleted file mode 100644 index 92526fe..0000000 Binary files a/sources/img/icons/syndicates/FactionSigilCavia.png and /dev/null differ diff --git a/sources/img/icons/syndicates/FactionVentKidz.png b/sources/img/icons/syndicates/FactionVentKidz.png deleted file mode 100644 index 49bff33..0000000 Binary files a/sources/img/icons/syndicates/FactionVentKidz.png and /dev/null differ diff --git a/sources/img/icons/syndicates/HexIcon.png b/sources/img/icons/syndicates/HexIcon.png deleted file mode 100644 index c3c3730..0000000 Binary files a/sources/img/icons/syndicates/HexIcon.png and /dev/null differ diff --git a/sources/img/icons/syndicates/IconCetusElder.png b/sources/img/icons/syndicates/IconCetusElder.png deleted file mode 100644 index 27933d9..0000000 Binary files a/sources/img/icons/syndicates/IconCetusElder.png and /dev/null differ diff --git a/sources/img/icons/syndicates/IconEntrati.png b/sources/img/icons/syndicates/IconEntrati.png deleted file mode 100644 index 5601772..0000000 Binary files a/sources/img/icons/syndicates/IconEntrati.png and /dev/null differ diff --git a/sources/img/icons/syndicates/IconSimaris.png b/sources/img/icons/syndicates/IconSimaris.png deleted file mode 100644 index c5941c7..0000000 Binary files a/sources/img/icons/syndicates/IconSimaris.png and /dev/null differ diff --git a/sources/img/icons/syndicates/IconSolaris.png b/sources/img/icons/syndicates/IconSolaris.png deleted file mode 100644 index 191b8e6..0000000 Binary files a/sources/img/icons/syndicates/IconSolaris.png and /dev/null differ diff --git a/sources/img/icons/syndicates/Loid.png b/sources/img/icons/syndicates/Loid.png deleted file mode 100644 index 673a4fc..0000000 Binary files a/sources/img/icons/syndicates/Loid.png and /dev/null differ diff --git a/sources/img/icons/syndicates/SolarisUnited1.png b/sources/img/icons/syndicates/SolarisUnited1.png deleted file mode 100644 index d985be3..0000000 Binary files a/sources/img/icons/syndicates/SolarisUnited1.png and /dev/null differ diff --git a/sources/img/icons/syndicates/TheHoldfastsIcon.png b/sources/img/icons/syndicates/TheHoldfastsIcon.png deleted file mode 100644 index 8fbbd12..0000000 Binary files a/sources/img/icons/syndicates/TheHoldfastsIcon.png and /dev/null differ diff --git a/sources/img/icons/syndicates/TheQuillsSigil.png b/sources/img/icons/syndicates/TheQuillsSigil.png deleted file mode 100644 index 5e39527..0000000 Binary files a/sources/img/icons/syndicates/TheQuillsSigil.png and /dev/null differ diff --git a/sources/img/icons/tasks/Acrithis.png b/sources/img/icons/tasks/Acrithis.png new file mode 100644 index 0000000..9f46203 Binary files /dev/null and b/sources/img/icons/tasks/Acrithis.png differ diff --git a/sources/img/icons/tasks/Bird3.png b/sources/img/icons/tasks/Bird3.png new file mode 100644 index 0000000..d008f4d Binary files /dev/null and b/sources/img/icons/tasks/Bird3.png differ diff --git a/sources/img/icons/tasks/Computer.png b/sources/img/icons/tasks/Computer.png new file mode 100644 index 0000000..bac4a73 Binary files /dev/null and b/sources/img/icons/tasks/Computer.png differ diff --git a/sources/img/icons/tasks/DoubleCreditEvent.png b/sources/img/icons/tasks/DoubleCreditEvent.png new file mode 100644 index 0000000..538ca5d Binary files /dev/null and b/sources/img/icons/tasks/DoubleCreditEvent.png differ diff --git a/sources/img/icons/tasks/ErgoGlast.png b/sources/img/icons/tasks/ErgoGlast.png new file mode 100644 index 0000000..6a28fb2 Binary files /dev/null and b/sources/img/icons/tasks/ErgoGlast.png differ diff --git a/sources/img/icons/tasks/FocusLensFocus.png b/sources/img/icons/tasks/FocusLensFocus.png new file mode 100644 index 0000000..c258437 Binary files /dev/null and b/sources/img/icons/tasks/FocusLensFocus.png differ diff --git a/sources/img/icons/GarrisonIcon.png b/sources/img/icons/tasks/GarrisonIcon.png similarity index 100% rename from sources/img/icons/GarrisonIcon.png rename to sources/img/icons/tasks/GarrisonIcon.png diff --git a/sources/img/icons/tasks/Heat_d.png b/sources/img/icons/tasks/Heat_d.png new file mode 100644 index 0000000..14788d3 Binary files /dev/null and b/sources/img/icons/tasks/Heat_d.png differ diff --git a/sources/img/icons/tasks/HelpClem_.png b/sources/img/icons/tasks/HelpClem_.png new file mode 100644 index 0000000..d9fc2e4 Binary files /dev/null and b/sources/img/icons/tasks/HelpClem_.png differ diff --git a/sources/img/icons/IconBuild.png b/sources/img/icons/tasks/IconBuild.png similarity index 100% rename from sources/img/icons/IconBuild.png rename to sources/img/icons/tasks/IconBuild.png diff --git a/sources/img/icons/IconCommand.png b/sources/img/icons/tasks/IconCommand.png similarity index 55% rename from sources/img/icons/IconCommand.png rename to sources/img/icons/tasks/IconCommand.png index 9145f3b..89d367e 100644 Binary files a/sources/img/icons/IconCommand.png and b/sources/img/icons/tasks/IconCommand.png differ diff --git a/sources/img/icons/tasks/IconDuviriCategory256.png b/sources/img/icons/tasks/IconDuviriCategory256.png new file mode 100644 index 0000000..57a2b62 Binary files /dev/null and b/sources/img/icons/tasks/IconDuviriCategory256.png differ diff --git a/sources/img/icons/tasks/IconHelminth.png b/sources/img/icons/tasks/IconHelminth.png new file mode 100644 index 0000000..6be06b1 Binary files /dev/null and b/sources/img/icons/tasks/IconHelminth.png differ diff --git a/sources/img/icons/tasks/IconNarmer.png b/sources/img/icons/tasks/IconNarmer.png new file mode 100644 index 0000000..7d7037f Binary files /dev/null and b/sources/img/icons/tasks/IconNarmer.png differ diff --git a/sources/img/icons/tasks/IconOmegaMod256.png b/sources/img/icons/tasks/IconOmegaMod256.png new file mode 100644 index 0000000..be0ab8e Binary files /dev/null and b/sources/img/icons/tasks/IconOmegaMod256.png differ diff --git a/sources/img/icons/IconPrimeParts.png b/sources/img/icons/tasks/IconPrimeParts.png similarity index 100% rename from sources/img/icons/IconPrimeParts.png rename to sources/img/icons/tasks/IconPrimeParts.png diff --git a/sources/img/icons/tasks/KIM/RetroPfpA.png b/sources/img/icons/tasks/KIM/RetroPfpA.png new file mode 100644 index 0000000..6b16b9f Binary files /dev/null and b/sources/img/icons/tasks/KIM/RetroPfpA.png differ diff --git a/sources/img/icons/tasks/KIM/RetroPfpB.png b/sources/img/icons/tasks/KIM/RetroPfpB.png new file mode 100644 index 0000000..19458d5 Binary files /dev/null and b/sources/img/icons/tasks/KIM/RetroPfpB.png differ diff --git a/sources/img/icons/tasks/KIM/RetroPfpC.png b/sources/img/icons/tasks/KIM/RetroPfpC.png new file mode 100644 index 0000000..4ca0630 Binary files /dev/null and b/sources/img/icons/tasks/KIM/RetroPfpC.png differ diff --git a/sources/img/icons/tasks/KIM/RetroPfpD.png b/sources/img/icons/tasks/KIM/RetroPfpD.png new file mode 100644 index 0000000..08ebd4b Binary files /dev/null and b/sources/img/icons/tasks/KIM/RetroPfpD.png differ diff --git a/sources/img/icons/tasks/KIM/RetroPfpE.png b/sources/img/icons/tasks/KIM/RetroPfpE.png new file mode 100644 index 0000000..e050749 Binary files /dev/null and b/sources/img/icons/tasks/KIM/RetroPfpE.png differ diff --git a/sources/img/icons/tasks/KIM/RetroPfpF.png b/sources/img/icons/tasks/KIM/RetroPfpF.png new file mode 100644 index 0000000..d7deefa Binary files /dev/null and b/sources/img/icons/tasks/KIM/RetroPfpF.png differ diff --git a/sources/img/icons/tasks/KIM/RetroPfpG.png b/sources/img/icons/tasks/KIM/RetroPfpG.png new file mode 100644 index 0000000..1d6636c Binary files /dev/null and b/sources/img/icons/tasks/KIM/RetroPfpG.png differ diff --git a/sources/img/icons/tasks/KIM/RetroPfpH.png b/sources/img/icons/tasks/KIM/RetroPfpH.png new file mode 100644 index 0000000..b7c3fbd Binary files /dev/null and b/sources/img/icons/tasks/KIM/RetroPfpH.png differ diff --git a/sources/img/icons/tasks/KIM/RetroPfpI.png b/sources/img/icons/tasks/KIM/RetroPfpI.png new file mode 100644 index 0000000..430772e Binary files /dev/null and b/sources/img/icons/tasks/KIM/RetroPfpI.png differ diff --git a/sources/img/icons/tasks/KIM/RetroPfpJ.png b/sources/img/icons/tasks/KIM/RetroPfpJ.png new file mode 100644 index 0000000..96183a4 Binary files /dev/null and b/sources/img/icons/tasks/KIM/RetroPfpJ.png differ diff --git a/sources/img/icons/tasks/KIM/RetroPfpM.png b/sources/img/icons/tasks/KIM/RetroPfpM.png new file mode 100644 index 0000000..d730281 Binary files /dev/null and b/sources/img/icons/tasks/KIM/RetroPfpM.png differ diff --git a/sources/img/icons/tasks/KIM/Tau_RetroPfpA.png b/sources/img/icons/tasks/KIM/Tau_RetroPfpA.png new file mode 100644 index 0000000..09b2d4a Binary files /dev/null and b/sources/img/icons/tasks/KIM/Tau_RetroPfpA.png differ diff --git a/sources/img/icons/tasks/KIM/Tau_RetroPfpB.png b/sources/img/icons/tasks/KIM/Tau_RetroPfpB.png new file mode 100644 index 0000000..6300d19 Binary files /dev/null and b/sources/img/icons/tasks/KIM/Tau_RetroPfpB.png differ diff --git a/sources/img/icons/tasks/KIM/Tau_RetroPfpC.png b/sources/img/icons/tasks/KIM/Tau_RetroPfpC.png new file mode 100644 index 0000000..5e4f1dc Binary files /dev/null and b/sources/img/icons/tasks/KIM/Tau_RetroPfpC.png differ diff --git a/sources/img/icons/tasks/KIM/Tau_RetroPfpD.png b/sources/img/icons/tasks/KIM/Tau_RetroPfpD.png new file mode 100644 index 0000000..af1a013 Binary files /dev/null and b/sources/img/icons/tasks/KIM/Tau_RetroPfpD.png differ diff --git a/sources/img/icons/tasks/Kaya_.png b/sources/img/icons/tasks/Kaya_.png new file mode 100644 index 0000000..464aa9b Binary files /dev/null and b/sources/img/icons/tasks/Kaya_.png differ diff --git a/sources/img/icons/tasks/LotusFlower.png b/sources/img/icons/tasks/LotusFlower.png new file mode 100644 index 0000000..919dd76 Binary files /dev/null and b/sources/img/icons/tasks/LotusFlower.png differ diff --git a/sources/img/icons/tasks/MagnifyingGlassIcon.png b/sources/img/icons/tasks/MagnifyingGlassIcon.png new file mode 100644 index 0000000..0398068 Binary files /dev/null and b/sources/img/icons/tasks/MagnifyingGlassIcon.png differ diff --git a/sources/img/icons/tasks/Market.png b/sources/img/icons/tasks/Market.png new file mode 100644 index 0000000..25a6272 Binary files /dev/null and b/sources/img/icons/tasks/Market.png differ diff --git a/sources/img/icons/tasks/Maroo.png b/sources/img/icons/tasks/Maroo.png new file mode 100644 index 0000000..a8b1679 Binary files /dev/null and b/sources/img/icons/tasks/Maroo.png differ diff --git a/sources/img/icons/tasks/MiniMapCaviaVendor_.png b/sources/img/icons/tasks/MiniMapCaviaVendor_.png new file mode 100644 index 0000000..8ccbd3d Binary files /dev/null and b/sources/img/icons/tasks/MiniMapCaviaVendor_.png differ diff --git a/sources/img/icons/tasks/MysteryShroom256_d.png b/sources/img/icons/tasks/MysteryShroom256_d.png new file mode 100644 index 0000000..994eeac Binary files /dev/null and b/sources/img/icons/tasks/MysteryShroom256_d.png differ diff --git a/sources/img/icons/tasks/Necraloid.png b/sources/img/icons/tasks/Necraloid.png new file mode 100644 index 0000000..a09e3fc Binary files /dev/null and b/sources/img/icons/tasks/Necraloid.png differ diff --git a/sources/img/icons/tasks/NetraRequiemIcon.png b/sources/img/icons/tasks/NetraRequiemIcon.png new file mode 100644 index 0000000..815669b Binary files /dev/null and b/sources/img/icons/tasks/NetraRequiemIcon.png differ diff --git a/sources/img/icons/tasks/NightwaveIconSimple.png b/sources/img/icons/tasks/NightwaveIconSimple.png new file mode 100644 index 0000000..c81a785 Binary files /dev/null and b/sources/img/icons/tasks/NightwaveIconSimple.png differ diff --git a/sources/img/icons/tasks/Nyx.png b/sources/img/icons/tasks/Nyx.png new file mode 100644 index 0000000..465dba5 Binary files /dev/null and b/sources/img/icons/tasks/Nyx.png differ diff --git a/sources/img/icons/README.md b/sources/img/icons/tasks/README.md similarity index 89% rename from sources/img/icons/README.md rename to sources/img/icons/tasks/README.md index e06fe7a..243c2a3 100644 --- a/sources/img/icons/README.md +++ b/sources/img/icons/tasks/README.md @@ -10,5 +10,5 @@ Icons should be monochromatic, square, and cropped to fill the entire canvas. resizing (requires [ImageMagick](https://imagemagick.org/)). Most images come as white and should be inverted so they're possible to see in Windows Explorer light mode. CSS converts it to all white or all black as appropriate for the -theme. If a monochrome icon is not available, you can add `noIconFilter: true` +theme. If a monochrome icon is not available, you can add `"noIconFilter": true` to the task and the icon will be shown in color as-is. diff --git a/sources/img/icons/ReputationSmall.png b/sources/img/icons/tasks/ReputationSmall.png similarity index 100% rename from sources/img/icons/ReputationSmall.png rename to sources/img/icons/tasks/ReputationSmall.png diff --git a/sources/img/icons/tasks/Sortie.png b/sources/img/icons/tasks/Sortie.png new file mode 100644 index 0000000..020bb6b Binary files /dev/null and b/sources/img/icons/tasks/Sortie.png differ diff --git a/sources/img/icons/tasks/SteelEssenceIcon.png b/sources/img/icons/tasks/SteelEssenceIcon.png new file mode 100644 index 0000000..f40782c Binary files /dev/null and b/sources/img/icons/tasks/SteelEssenceIcon.png differ diff --git a/sources/img/icons/tasks/Wisp.png b/sources/img/icons/tasks/Wisp.png new file mode 100644 index 0000000..13a1835 Binary files /dev/null and b/sources/img/icons/tasks/Wisp.png differ diff --git a/sources/img/icons/tasks/Yonta.png b/sources/img/icons/tasks/Yonta.png new file mode 100644 index 0000000..b076de6 Binary files /dev/null and b/sources/img/icons/tasks/Yonta.png differ diff --git a/sources/img/icons/tasks/ZorbaQuickAccessIcon.png b/sources/img/icons/tasks/ZorbaQuickAccessIcon.png new file mode 100644 index 0000000..e12b073 Binary files /dev/null and b/sources/img/icons/tasks/ZorbaQuickAccessIcon.png differ diff --git a/sources/img/icons/resize.sh b/sources/img/icons/tasks/resize.sh similarity index 100% rename from sources/img/icons/resize.sh rename to sources/img/icons/tasks/resize.sh diff --git a/sources/img/icons/tasks/syndicates/FactionSigilAssassins.png b/sources/img/icons/tasks/syndicates/FactionSigilAssassins.png new file mode 100644 index 0000000..aeb41c5 Binary files /dev/null and b/sources/img/icons/tasks/syndicates/FactionSigilAssassins.png differ diff --git a/sources/img/icons/tasks/syndicates/FactionSigilBusiness.png b/sources/img/icons/tasks/syndicates/FactionSigilBusiness.png new file mode 100644 index 0000000..59852b3 Binary files /dev/null and b/sources/img/icons/tasks/syndicates/FactionSigilBusiness.png differ diff --git a/sources/img/icons/tasks/syndicates/FactionSigilCavia.png b/sources/img/icons/tasks/syndicates/FactionSigilCavia.png new file mode 100644 index 0000000..c186058 Binary files /dev/null and b/sources/img/icons/tasks/syndicates/FactionSigilCavia.png differ diff --git a/sources/img/icons/tasks/syndicates/FactionSigilChurch.png b/sources/img/icons/tasks/syndicates/FactionSigilChurch.png new file mode 100644 index 0000000..911bbea Binary files /dev/null and b/sources/img/icons/tasks/syndicates/FactionSigilChurch.png differ diff --git a/sources/img/icons/tasks/syndicates/FactionSigilJudge.png b/sources/img/icons/tasks/syndicates/FactionSigilJudge.png new file mode 100644 index 0000000..c173d70 Binary files /dev/null and b/sources/img/icons/tasks/syndicates/FactionSigilJudge.png differ diff --git a/sources/img/icons/tasks/syndicates/FactionSigilOracle.png b/sources/img/icons/tasks/syndicates/FactionSigilOracle.png new file mode 100644 index 0000000..7503c66 Binary files /dev/null and b/sources/img/icons/tasks/syndicates/FactionSigilOracle.png differ diff --git a/sources/img/icons/tasks/syndicates/FactionSigilRebels.png b/sources/img/icons/tasks/syndicates/FactionSigilRebels.png new file mode 100644 index 0000000..1653c3b Binary files /dev/null and b/sources/img/icons/tasks/syndicates/FactionSigilRebels.png differ diff --git a/sources/img/icons/tasks/syndicates/FactionVentKidz.png b/sources/img/icons/tasks/syndicates/FactionVentKidz.png new file mode 100644 index 0000000..6f2febb Binary files /dev/null and b/sources/img/icons/tasks/syndicates/FactionVentKidz.png differ diff --git a/sources/img/icons/tasks/syndicates/HexIcon.png b/sources/img/icons/tasks/syndicates/HexIcon.png new file mode 100644 index 0000000..9f3038b Binary files /dev/null and b/sources/img/icons/tasks/syndicates/HexIcon.png differ diff --git a/sources/img/icons/tasks/syndicates/IconCetusElder.png b/sources/img/icons/tasks/syndicates/IconCetusElder.png new file mode 100644 index 0000000..1474bed Binary files /dev/null and b/sources/img/icons/tasks/syndicates/IconCetusElder.png differ diff --git a/sources/img/icons/tasks/syndicates/IconEntrati.png b/sources/img/icons/tasks/syndicates/IconEntrati.png new file mode 100644 index 0000000..1aaed84 Binary files /dev/null and b/sources/img/icons/tasks/syndicates/IconEntrati.png differ diff --git a/sources/img/icons/tasks/syndicates/IconSimaris.png b/sources/img/icons/tasks/syndicates/IconSimaris.png new file mode 100644 index 0000000..e595448 Binary files /dev/null and b/sources/img/icons/tasks/syndicates/IconSimaris.png differ diff --git a/sources/img/icons/tasks/syndicates/IconSolaris.png b/sources/img/icons/tasks/syndicates/IconSolaris.png new file mode 100644 index 0000000..c82660f Binary files /dev/null and b/sources/img/icons/tasks/syndicates/IconSolaris.png differ diff --git a/sources/img/icons/tasks/syndicates/Loid.png b/sources/img/icons/tasks/syndicates/Loid.png new file mode 100644 index 0000000..7806698 Binary files /dev/null and b/sources/img/icons/tasks/syndicates/Loid.png differ diff --git a/sources/img/icons/tasks/syndicates/SolarisUnited1.png b/sources/img/icons/tasks/syndicates/SolarisUnited1.png new file mode 100644 index 0000000..3cd7f62 Binary files /dev/null and b/sources/img/icons/tasks/syndicates/SolarisUnited1.png differ diff --git a/sources/img/icons/tasks/syndicates/TheHoldfastsIcon.png b/sources/img/icons/tasks/syndicates/TheHoldfastsIcon.png new file mode 100644 index 0000000..69634f5 Binary files /dev/null and b/sources/img/icons/tasks/syndicates/TheHoldfastsIcon.png differ diff --git a/sources/img/icons/tasks/syndicates/TheQuillsSigil.png b/sources/img/icons/tasks/syndicates/TheQuillsSigil.png new file mode 100644 index 0000000..866e79d Binary files /dev/null and b/sources/img/icons/tasks/syndicates/TheQuillsSigil.png differ diff --git a/sources/img/perita.webp b/sources/img/perita.webp new file mode 100644 index 0000000..5b750d1 Binary files /dev/null and b/sources/img/perita.webp differ diff --git a/sources/index.html b/sources/index.html index 41da2f0..4ee4988 100644 --- a/sources/index.html +++ b/sources/index.html @@ -5,12 +5,17 @@ Warframe Task Checklist - - <%- criticalCss %> + + - + diff --git a/sources/js/app.js b/sources/js/app.js index 94d0589..194af04 100644 --- a/sources/js/app.js +++ b/sources/js/app.js @@ -1,6 +1,24 @@ // --- sources/js/app.js --- console.log(`vite mode: ${import.meta.env.MODE}`); +import { + modulo, + iconURL, + makeCycleIcon, + formatTimestamp, + getMostRecentMondayMidnightUTC, + getNextDailyMidnightUTC, + getUTCDateString, + getUTCDayOfYear, + formatCountdown, + parseDuration, + calcCycleNumber, + makeInfoLineItem, + calcTaskTimes, +} from "./functions.js"; + +import * as C from "./constants.js"; + import * as svgIcons from "./icons.js"; // --- Configuration --- @@ -8,45 +26,47 @@ import * as svgIcons from "./icons.js"; const dailyBackgroundImageIds = [ 'bg-image-0', 'bg-image-1', - 'bg-image-2' + 'bg-image-2', + 'bg-image-3', + 'bg-image-4', // Add more IDs if you add more background image divs in HTML ]; -const APP_VERSION = "4.0"; +const APP_VERSION = "5.0"; const GIT_COMMIT_HASH_LONG = import.meta.env.VITE_GIT_COMMIT_HASH; const GIT_COMMIT_HASH = GIT_COMMIT_HASH_LONG.slice(0,7); -const WARFRAME_VERSION = "41.0.7"; +const WARFRAME_VERSION = "42.0.11"; const THEME_STORAGE_KEY = 'warframeChecklistTheme'; -function getStorageKey(appVersion) { - const versionParts = appVersion.split('.'); - if (versionParts.length >= 1) { - const major = versionParts[0]; - return `warframeChecklistData_v${major}`; - } - return `warframeChecklistData_v${appVersion.replace(/\./g, '_')}`; -} -const DATA_STORAGE_KEY = getStorageKey(APP_VERSION); - -const baroKiTeerData = { - referenceArrivalUTC: new Date(Date.UTC(2025, 4, 30, 13, 0, 0)).getTime(), - cycleMilliseconds: 14 * 24 * 60 * 60 * 1000, - durationMilliseconds: 48 * 60 * 60 * 1000, -}; +// only update DATA_STORAGE_KEY when the data storage format changes +// in a backwards-incompatible way (this should be *very* rare) +const DATA_STORAGE_KEY = "warframeChecklistData_format1"; // --- Task Data --- import tasks from "./tasks.json" with {type: "json"}; - -const taskIcons = import.meta.glob("../img/icons/**/*.png", {eager: true, query: '?url', import: 'default'}); -function iconURL(iconName) { - return taskIcons["../img/icons/" + iconName]; +import cycles from "./cycles.json" with {type: "json"}; +import moreInfo from "./moreInfo.js"; + +function _prepTasks() { + function prep(period) { + return (task) => { + if (task.ref) { // alternate ref tasks need a period for countdown and reset to work correctly + task.period = period; + } + if (task.id in moreInfo) { + task.moreInfo = moreInfo[task.id]; + } + } + } + tasks.daily.forEach(prep("1d")); + tasks.weekly.forEach(prep("7d")); } +_prepTasks(); // --- DOM Elements (defined after DOMContentLoaded) --- -let bodyElement, contentElement, themeToggleButton, hamburgerButton, slideoutMenuOverlay, menuContentBox, menuCloseButton, - dailyList, weeklyList, otherList, resetDailyButton, resetWeeklyButton, resetButton, unhideTasksButton, - lastSavedTimestampElement, saveStatusElement, sectionToggles, dailyResetTimeElement, weeklyResetTimeElement, - errorDisplayElement, errorMessageElement, errorCloseButton, errorCopyButton, appVersionElement, gitHashElement, wfVersionElement, - backgroundDivs = []; +let bodyElement, themeToggleButton, hamburgerButton, optionsMenu, resetDailyButton, resetWeeklyButton, resetButton, + unhideTasksButton, lastSavedTimestampElement, saveStatusElement, sectionToggles, dailyResetTimeElement, + weeklyResetTimeElement, errorDisplayElement, errorMessageElement, errorCloseButton, errorCopyButton, + appVersionElement, gitHashElement, wfVersionElement, scheduleDialog, moreInfoDialog, backgroundDivs = []; // --- State Variables --- @@ -64,7 +84,7 @@ let checklistData = { lastWeeklyReset: null, hiddenTasks: {}, manuallyHiddenSections: {}, - lastEightHourResets: {}, + lastTaskResetTimes: {}, notificationPreferences: {}, notificationsSent: {} }; @@ -77,15 +97,9 @@ let countdownInterval; function initializeDOMElements() { bodyElement = document.body; - contentElement = document.querySelector('.checklist-content'); themeToggleButton = document.getElementById('theme-toggle-button'); hamburgerButton = document.getElementById('hamburger-button'); - slideoutMenuOverlay = document.getElementById('slideout-menu-overlay'); - menuContentBox = document.getElementById('menu-content-box'); - menuCloseButton = document.getElementById('menu-close-button'); - dailyList = document.querySelector('#daily-tasks-content ul'); - weeklyList = document.querySelector('#weekly-tasks-content ul'); - otherList = document.querySelector('#other-tasks-content ul'); + optionsMenu = document.getElementById("options-menu"); resetDailyButton = document.getElementById('reset-daily-button'); resetWeeklyButton = document.getElementById('reset-weekly-button'); resetButton = document.getElementById('reset-button'); @@ -102,9 +116,11 @@ function initializeDOMElements() { appVersionElement = document.querySelector('.version-text'); gitHashElement = document.querySelector('.git-hash-text'); wfVersionElement = document.querySelector('.warframe-version-text'); + scheduleDialog = document.getElementById("cycle-schedule"); + moreInfoDialog = document.getElementById("more-info"); backgroundDivs = []; - dailyBackgroundImageIds.forEach(id => { + dailyBackgroundImageIds.forEach((id) => { const el = document.getElementById(id); if (el) { backgroundDivs.push(el); @@ -115,41 +131,47 @@ function initializeDOMElements() { } function displayError(message) { - if (!errorDisplayElement || !errorMessageElement) return; + if (!errorDisplayElement || !errorMessageElement) { return; } console.error("Displaying Error:", message); errorMessageElement.textContent = message; - errorDisplayElement.classList.add('visible'); - if (errorCopyButton) errorCopyButton.textContent = 'Copy'; + errorDisplayElement.classList.add("visible"); + if (errorCopyButton) { errorCopyButton.textContent = "Copy"; } } function hideError() { - if (!errorDisplayElement) return; - errorDisplayElement.classList.remove('visible'); - errorMessageElement.textContent = ''; - if (errorCopyButton) errorCopyButton.textContent = 'Copy'; + if (!errorDisplayElement) { return; } + errorDisplayElement.classList.remove("visible"); + errorMessageElement.textContent = ''; + if (errorCopyButton) { errorCopyButton.textContent = "Copy"; } } function copyErrorToClipboard() { const errorMessage = errorMessageElement.textContent; if (!errorMessage || !navigator.clipboard) { console.warn("Clipboard API not available or no error message to copy."); - if (errorCopyButton) errorCopyButton.textContent = 'Failed'; - setTimeout(() => { if (errorCopyButton) errorCopyButton.textContent = 'Copy'; }, 2000); + if (errorCopyButton) { errorCopyButton.textContent = "Failed"; } + setTimeout(() => { + if (errorCopyButton) { errorCopyButton.textContent = "Copy"; } + }, 2000); return; } navigator.clipboard.writeText(errorMessage).then(() => { console.log("Error message copied to clipboard."); - if (errorCopyButton) errorCopyButton.textContent = 'Copied!'; - setTimeout(() => { if (errorCopyButton) errorCopyButton.textContent = 'Copy'; }, 2000); - }).catch(err => { - console.error('Failed to copy error message: ', err); - if (errorCopyButton) errorCopyButton.textContent = 'Failed'; - setTimeout(() => { if (errorCopyButton) errorCopyButton.textContent = 'Copy'; }, 2000); + if (errorCopyButton) { errorCopyButton.textContent = "Copied!"; } + setTimeout(() => { + if (errorCopyButton) { errorCopyButton.textContent = "Copy"; } + }, 2000); + }).catch((err) => { + console.error("Failed to copy error message: ", err); + if (errorCopyButton) { errorCopyButton.textContent = "Failed"; } + setTimeout(() => { + if (errorCopyButton) { errorCopyButton.textContent = "Copy"; } + }, 2000); }); } function applyTheme(theme) { - if (!bodyElement || !themeToggleButton) return; + if (!bodyElement || !themeToggleButton) { return; } if (theme === 'light') { bodyElement.classList.add('light-mode'); @@ -191,63 +213,21 @@ function loadThemePreference() { } } - -function formatTimestamp(timestamp) { - if (!timestamp) return 'Never'; - try { - const date = new Date(timestamp); - if (isNaN(date.getTime())) return 'Invalid Date'; - return date.toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' }); - } catch (e) { console.error("Error formatting timestamp:", e); return 'Error'; } -} - function updateLastSavedDisplay(timestamp) { if (lastSavedTimestampElement) { lastSavedTimestampElement.textContent = `Last saved: ${formatTimestamp(timestamp)}`; } else { - console.error("lastSavedTimestampElement not found"); + console.error("lastSavedTimestampElement not found"); } } function showSaveStatus() { - if (!saveStatusElement) return; + if (!saveStatusElement) { return; } clearTimeout(saveStatusTimeout); saveStatusElement.style.opacity = '1'; saveStatusTimeout = setTimeout(() => { saveStatusElement.style.opacity = '0'; }, 1500); } -function getMostRecentMondayMidnightUTC() { - const now = new Date(); - const currentUTCDay = now.getUTCDay(); - const daysSinceMondayUTC = (currentUTCDay === 0) ? 6 : currentUTCDay - 1; - - const mondayUTC = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())); - mondayUTC.setUTCDate(mondayUTC.getUTCDate() - daysSinceMondayUTC); - mondayUTC.setUTCHours(0, 0, 0, 0); - return mondayUTC.getTime(); -} - -function getNextDailyMidnightUTC() { - const now = new Date(); - const tomorrowUTC = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1)); - tomorrowUTC.setUTCHours(0, 0, 0, 0); - return tomorrowUTC.getTime(); -} - -function getUTCDateString(dateObj) { - const year = dateObj.getUTCFullYear(); - const month = (dateObj.getUTCMonth() + 1).toString().padStart(2, '0'); - const day = dateObj.getUTCDate().toString().padStart(2, '0'); - return `${year}-${month}-${day}`; -} - -function getUTCDayOfYear(date) { - const startOfYear = Date.UTC(date.getUTCFullYear(), 0, 0); - const diff = date.getTime() - startOfYear; - const oneDay = 1000 * 60 * 60 * 24; - return Math.floor(diff / oneDay); -} - function setDailyBackground() { if (!bodyElement || backgroundDivs.length === 0) { // Check backgroundDivs instead of bodyElement.style console.warn("Body element or background divs not ready for setDailyBackground."); @@ -271,168 +251,70 @@ function setDailyBackground() { }); } -function formatCountdown(ms) { - if (ms < 0) return "00:00:00"; - - let totalSeconds = Math.floor(ms / 1000); - let days = Math.floor(totalSeconds / (24 * 60 * 60)); - totalSeconds %= (24 * 60 * 60); - let hours = Math.floor(totalSeconds / (60 * 60)); - totalSeconds %= (60 * 60); - let minutes = Math.floor(totalSeconds / 60); - let seconds = totalSeconds % 60; - - const pad = (num) => String(num).padStart(2, '0'); - - if (days > 0) { - return `${days}d ${pad(hours)}:${pad(minutes)}:${pad(seconds)}`; - } - return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`; -} - -function calculateBaroTimings() { - const now = new Date().getTime(); - let nextArrival = baroKiTeerData.referenceArrivalUTC; - - while (nextArrival < now - baroKiTeerData.durationMilliseconds) { - nextArrival += baroKiTeerData.cycleMilliseconds; - } - if (now >= nextArrival + baroKiTeerData.durationMilliseconds) { - nextArrival += baroKiTeerData.cycleMilliseconds; - } - - const departure = nextArrival + baroKiTeerData.durationMilliseconds; - let status = "arriving"; - if (now >= nextArrival && now < departure) { - status = "here"; - } else if (now >= departure) { - status = "departed"; - } - - return { - nextArrivalTimestamp: nextArrival, - departureTimestamp: departure, - status: status - }; -} - -function displayBaroCountdown() { - const baroCountdownElement = document.getElementById('baro-countdown-timer'); - if (!baroCountdownElement) return; - - const timings = calculateBaroTimings(); - const now = new Date().getTime(); - let countdownText = ""; - const arrivalNotificationId = `baro_arrival_${timings.nextArrivalTimestamp}`; - const departureNotificationId = `baro_departure_${timings.departureTimestamp}`; - - - if (timings.status === "here") { - const diff = timings.departureTimestamp - now; - countdownText = `Leaves in ${formatCountdown(diff)}`; - if (checklistData.notificationPreferences['other_baro'] && !checklistData.notificationsSent[arrivalNotificationId]) { - showNotification("Baro Ki'Teer Has Arrived!", "Check his inventory for exclusive items."); - checklistData.notificationsSent[arrivalNotificationId] = true; - saveData(false); - } - if (diff > 0 && diff < 60 * 60 * 1000 && checklistData.notificationPreferences['other_baro'] && !checklistData.notificationsSent[departureNotificationId]) { - showNotification("Baro Ki'Teer Departing Soon!", `Leaves in approximately ${Math.round(diff / (60 * 1000))} minutes.`); - checklistData.notificationsSent[departureNotificationId] = true; - saveData(false); - } - - } else { - let nextArrivalForCountdown = timings.nextArrivalTimestamp; - const diff = nextArrivalForCountdown - now; - countdownText = `Arrives in ${formatCountdown(diff)}`; - if (timings.status === "departed") { - const prevArrivalId = `baro_arrival_${timings.nextArrivalTimestamp - baroKiTeerData.cycleMilliseconds}`; - const prevDepartureId = `baro_departure_${timings.departureTimestamp - baroKiTeerData.cycleMilliseconds}`; - if(checklistData.notificationsSent[prevArrivalId]) delete checklistData.notificationsSent[prevArrivalId]; - if(checklistData.notificationsSent[prevDepartureId]) delete checklistData.notificationsSent[prevDepartureId]; - if(checklistData.notificationsSent[departureNotificationId]) delete checklistData.notificationsSent[departureNotificationId]; - } - } - baroCountdownElement.textContent = countdownText; -} - -function getNextEightHourResetUTC() { - const now = new Date(); - const currentUTCHour = now.getUTCHours(); - let nextResetDate = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())); - - if (currentUTCHour < 8) { - nextResetDate.setUTCHours(8, 0, 0, 0); - } else if (currentUTCHour < 16) { - nextResetDate.setUTCHours(16, 0, 0, 0); - } else { - nextResetDate.setUTCDate(nextResetDate.getUTCDate() + 1); - nextResetDate.setUTCHours(0, 0, 0, 0); - } - return nextResetDate.getTime(); -} - -function displayEightHourTaskCountdown(taskElementId, countdownSpanId) { - const countdownSpan = document.getElementById(countdownSpanId); - if (!countdownSpan) return; - - const nextResetTimestamp = getNextEightHourResetUTC(); - const now = new Date().getTime(); - const diff = nextResetTimestamp - now; - countdownSpan.textContent = `(Resets in ${formatCountdown(diff)})`; +function handleResets() { + runAutoResets(); + displayLocalResetTimes(); } function displayLocalResetTimes() { try { const now = new Date().getTime(); + // Daily const nextDailyResetTimestamp = getNextDailyMidnightUTC(); const dailyDiff = nextDailyResetTimestamp - now; if (dailyResetTimeElement) { - dailyResetTimeElement.textContent = `(Resets in ${formatCountdown(dailyDiff)})`; + dailyResetTimeElement.innerHTML = `(Resets in ${formatCountdown(dailyDiff)})`; } + // Weekly let nextWeeklyResetTimestamp = getMostRecentMondayMidnightUTC(); if (now >= nextWeeklyResetTimestamp) { - nextWeeklyResetTimestamp += 7 * 24 * 60 * 60 * 1000; + nextWeeklyResetTimestamp += 7 * C.MILLISECONDS_PER_DAY; } const weeklyDiff = nextWeeklyResetTimestamp - now; if (weeklyResetTimeElement) { - weeklyResetTimeElement.textContent = `(Resets in ${formatCountdown(weeklyDiff)})`; + weeklyResetTimeElement.innerHTML = `(Resets in ${formatCountdown(weeklyDiff)})`; } - displayBaroCountdown(); - tasks.other.forEach(task => { - if (task.isEightHourTask) { - const countdownSpanId = task.id.replace(/^other_/, '') + '-countdown-timer'; - displayEightHourTaskCountdown(task.id, countdownSpanId); - } - }); - - runAutoResets() - + // Other + tasks.daily.forEach((task) => displayOtherTaskCountdown(task)); + tasks.weekly.forEach((task) => displayOtherTaskCountdown(task)); + tasks.other.forEach((task) => displayOtherTaskCountdown(task)); } catch (e) { console.error("Error calculating or displaying local reset times:", e); - if (dailyResetTimeElement) dailyResetTimeElement.textContent = `(Resets 00:00 UTC)`; - if (weeklyResetTimeElement) weeklyResetTimeElement.textContent = `(Resets Mon 00:00 UTC)`; + if (dailyResetTimeElement) { dailyResetTimeElement.innerHTML = `(Resets 00:00 UTC)`; } + if (weeklyResetTimeElement) { weeklyResetTimeElement.innerHTML = `(Resets Mon 00:00 UTC)`; } } } -function getStartOfCurrentEightHourCycleUTC() { +export function displayOtherTaskCountdown(task) { + const resetTimer = document.querySelector(`#${task.id} ~ .task-description .other-countdown`); + if (!resetTimer) { return; } + const now = new Date(); - const currentUTCHour = now.getUTCHours(); - let cycleStartHour; + const taskTimes = calcTaskTimes(task, now); - if (currentUTCHour < 8) { - cycleStartHour = 0; - } else if (currentUTCHour < 16) { - cycleStartHour = 8; - } else { - cycleStartHour = 16; + if (task.duration) { // intermittently available task (e.g., Baro) + const leaveNotifId = `${task.id}/departure`; + + if (taskTimes.isAvailable) { + const diff = taskTimes.thisCycleLeaveTimestamp - now.getTime(); + resetTimer.innerHTML = `(Available for ${formatCountdown(diff)})`; + + // Leaving soon notification (Arrival notification is handled in runAutoResets, the same as always available tasks) + if (diff < C.MILLISECONDS_PER_HOUR && checklistData.notificationPreferences[task.id] && checklistData.notificationsSent[leaveNotifId] !== cycleNumber) { + showNotification(`${task.text.split(":")[0]} Leaving Soon!`, `Approximately ${Math.round(diff / C.MILLISECONDS_PER_MINUTE)} minutes remaining.`); + checklistData.notificationsSent[leaveNotifId] = cycleNumber; + saveData(false); + } + } else { // task not available + resetTimer.innerHTML = `(Available in ${formatCountdown(taskTimes.nextResetTimestamp - now.getTime())})`; + if (checklistData.notificationsSent[leaveNotifId]) {delete checklistData.notificationsSent[leaveNotifId];} + } + } else { // always available task + resetTimer.innerHTML = `(Resets in ${formatCountdown(taskTimes.nextResetTimestamp - now.getTime())})`; } - const cycleStartDate = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())); - cycleStartDate.setUTCHours(cycleStartHour, 0, 0, 0); - return cycleStartDate.getTime(); } function runAutoResets() { @@ -444,7 +326,7 @@ function runAutoResets() { let lastDailyResetUTCString = lastDailyResetDate ? getUTCDateString(lastDailyResetDate) : null; if (!lastDailyResetUTCString || lastDailyResetUTCString !== todayUTCString) { console.log(`Performing daily auto-reset (UTC). Today: ${todayUTCString}, Last Reset: ${lastDailyResetUTCString}`); - resetSection("daily"); + resetSection("daily", false); } const lastWeeklyResetTimestamp = checklistData.lastWeeklyReset ? new Date(checklistData.lastWeeklyReset).getTime() : null; @@ -452,64 +334,64 @@ function runAutoResets() { if (!lastWeeklyResetTimestamp || lastWeeklyResetTimestamp < mostRecentMondayUTCTimestamp) { if (nowUTCTimestamp >= mostRecentMondayUTCTimestamp) { console.log(`Performing weekly auto-reset (UTC). Current Time: ${nowUTCTimestamp}, Last Reset: ${lastWeeklyResetTimestamp}, Target Monday: ${mostRecentMondayUTCTimestamp}`); - resetSection("weekly"); + resetSection("weekly", false); } } - let didResetOther = false; - const startOfCurrentEightHourCycle = getStartOfCurrentEightHourCycleUTC(); - if (!checklistData.lastEightHourResets) checklistData.lastEightHourResets = {}; - if (!checklistData.notificationsSent) checklistData.notificationsSent = {}; - - tasks.other.forEach(task => { - if (task.isEightHourTask) { - const lastResetForThisTask = checklistData.lastEightHourResets[task.id]; - const notificationId = `${task.id}_${startOfCurrentEightHourCycle}`; - - if (!lastResetForThisTask || lastResetForThisTask < startOfCurrentEightHourCycle) { - if (checklistData.progress[task.id] && !checklistData.hiddenTasks[task.id]) { - checklistData.progress[task.id] = false; - console.log(`Resetting 8-hour task: ${task.id}`); - didResetOther = true; - } - checklistData.lastEightHourResets[task.id] = startOfCurrentEightHourCycle; - if(checklistData.notificationsSent[notificationId]) { - delete checklistData.notificationsSent[notificationId]; - } - } - if (nowUTCTimestamp >= startOfCurrentEightHourCycle && - nowUTCTimestamp < startOfCurrentEightHourCycle + 60000 && - checklistData.notificationPreferences[task.id] && - !checklistData.notificationsSent[notificationId]) { - - const taskText = task.text.substring(0, task.text.indexOf(':')); - showNotification(`${taskText} has reset!`, "Vendor stock may have updated."); - checklistData.notificationsSent[notificationId] = true; - } + if (!checklistData.lastTaskResetTimes) { checklistData.lastTaskResetTimes = {}; } + if (!checklistData.notificationsSent) { checklistData.notificationsSent = {}; } + + for (const section of ["daily", "weekly", "other"]) { + const didReset = tasks[section].map(otherTaskReset).some((r) => r); + if (didReset) { + saveData(); + populateSection(section); } - }); - if (didResetOther) { - saveData(); - populateSection(otherList, tasks.other, checklistData.progress); - updateSectionControls('other-tasks-section') } } -function saveData(showStatus = true) { - hideError(); - checklistData.lastSaved = new Date().toISOString(); - try { - localStorage.setItem(DATA_STORAGE_KEY, JSON.stringify(checklistData)); - updateLastSavedDisplay(checklistData.lastSaved); - if (showStatus) { showSaveStatus(); } - } catch (e) { - console.error("Error saving data to localStorage:", e); - let userMessage = "Could not save progress."; - if (e.name === 'QuotaExceededError' || (e.code && (e.code === 22 || e.code === 1014))) { - userMessage = "Could not save progress. Browser storage might be full."; +function otherTaskReset(task) { + const section = task.id.split("_")[0]; + if (["daily", "weekly"].includes(section) && !task.ref) { // daily and weekly tasks with an alternate ref are handled as "other" tasks + return false; + } else if (!task.period) { + console.error(`[${task.id}] other_* tasks MUST specify a "period"`); + return false; + } + if (!task.ref) { + console.warn(`[${task.id}] other_* tasks SHOULD specify a "ref". The default ref of 0 ("1970-01-01T00:00:00Z") will be used otherwise.`) + } + + let didReset = false; + + const now = new Date(); + const cycleNumber = calcCycleNumber(task, now); + const lastResetTime = checklistData.lastTaskResetTimes[task.id] || 0; + const lastResetCycleNumber = calcCycleNumber(task, lastResetTime); + if (cycleNumber > lastResetCycleNumber || !calcTaskTimes(task, now).isAvailable) { + // Reset task + if (checklistData.progress[task.id] && !checklistData.hiddenTasks[task.id]) { + checklistData.progress[task.id] = false; + console.log(`Resetting task: ${task.id}`); + didReset = true; + } + checklistData.lastTaskResetTimes[task.id] = now.getTime(); + + // Delete old notification record + const notifSent = checklistData.notificationsSent[task.id]; + if (notifSent && notifSent !== cycleNumber) { + delete checklistData.notificationsSent[task.id]; + } + + // Send and record new notification + if (checklistData.notificationPreferences[task.id] && checklistData.notificationsSent[task.id] !== cycleNumber) { + showNotification(`${task.text.split(":")[0]} has reset!`, "Vendor stock may have updated."); + checklistData.notificationsSent[task.id] = cycleNumber; + saveData(false); } - displayError(userMessage); } + + return didReset; } async function requestNotificationPermission() { @@ -542,32 +424,48 @@ function showNotification(title, body) { } function createChecklistItem(task, isChecked, isSubtask = false) { + const isAvailable = calcTaskTimes(task, new Date()).isAvailable; + const listItem = document.createElement('li'); listItem.classList.add('task-item'); if (checklistData.hiddenTasks[task.id]) { listItem.classList.add('hidden-task'); } + // Checkbox const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = task.id; checkbox.checked = isChecked; - checkbox.classList.add('position-relative'); + if (!isAvailable) { + checkbox.indeterminate = true; + } if (isSubtask) { checkbox.dataset.parentId = task.parentId; } + // Lock unavailable task + checkbox.addEventListener("click", (event) => { + // onchange happens *after* the checkbox state has changed. onclick happens *before*. + if (!isAvailable) { + event.preventDefault(); // changing the state of the checkbox and then firing onchange is the default action. Don't do it. + } + }); + + // Task Icon const icon = document.createElement('img'); if (task.icon) { - icon.src = iconURL(task.icon); + icon.src = iconURL(`tasks/${task.icon}`); + icon.classList.add("task-icon"); if (!task.noIconFilter) { icon.classList.add('icon-filter') } } + // Hide/Notif Controls const controlsContainer = document.createElement('div'); - controlsContainer.classList.add('flex', 'items-center', 'ml-auto'); + // Notif Button if (task.id.startsWith('other_')) { const notificationButton = document.createElement('button'); notificationButton.classList.add('notification-toggle-btn'); @@ -575,7 +473,7 @@ function createChecklistItem(task, isChecked, isSubtask = false) { notificationButton.title = `Toggle notifications for ${task.text.split(':')[0]}`; notificationButton.innerHTML = svgIcons.bellIcon; - if(checklistData.notificationPreferences[task.id]) notificationButton.classList.add('active'); + if (checklistData.notificationPreferences[task.id]) { notificationButton.classList.add("active"); } notificationButton.addEventListener('click', async (e) => { e.stopPropagation(); @@ -590,6 +488,7 @@ function createChecklistItem(task, isChecked, isSubtask = false) { controlsContainer.appendChild(notificationButton); } + // Hide Button const hideButton = document.createElement('button'); hideButton.classList.add('hide-task-btn'); hideButton.setAttribute('aria-label', `Hide task: ${task.text.split(':')[0]}`); @@ -604,130 +503,152 @@ function createChecklistItem(task, isChecked, isSubtask = false) { }); controlsContainer.appendChild(hideButton); - if (task.isParent) { + if (task.subtasks) { listItem.classList.add('parent-task-container'); const parentHeaderDiv = document.createElement('div'); - parentHeaderDiv.classList.add('parent-task-header', 'flex', 'items-center', 'mb-1', 'w-full'); + parentHeaderDiv.classList.add('parent-task-header'); parentHeaderDiv.setAttribute('aria-expanded', 'true'); parentHeaderDiv.setAttribute('aria-controls', `${task.id}-subtasks`); parentHeaderDiv.appendChild(checkbox); if (task.icon) { parentHeaderDiv.appendChild(icon); } - const taskTextSpan = document.createElement('span'); - taskTextSpan.textContent = task.text; - taskTextSpan.classList.add('task-text', 'ml-2', 'flex-grow', 'cursor-pointer'); - if (isChecked) taskTextSpan.classList.add('checked'); - + // Task Text & Info Line + const taskDescription = document.createElement("div"); + taskDescription.classList.add("task-description"); + const taskText = document.createElement('span'); + taskText.textContent = task.text; + taskText.classList.add("task-text"); + if (isChecked) {taskDescription.classList.add("checked");} + if (!isAvailable) {taskDescription.classList.add("unavailable");} + taskDescription.appendChild(taskText); + makeInfoLine(task, taskDescription); + parentHeaderDiv.appendChild(taskDescription); + + // Collapse Button const collapseIcon = document.createElement("div"); collapseIcon.setAttribute('class', 'collapse-icon'); collapseIcon.innerHTML = svgIcons.collapseIcon; - parentHeaderDiv.appendChild(taskTextSpan); parentHeaderDiv.appendChild(controlsContainer); parentHeaderDiv.appendChild(collapseIcon); listItem.appendChild(parentHeaderDiv); + // Subtasks + const subtaskCollapsible = document.createElement("div"); + subtaskCollapsible.classList.add("subtask-collapsible"); const subtaskList = document.createElement('ul'); subtaskList.id = `${task.id}-subtasks`; - subtaskList.classList.add('list-none', 'pl-0', 'mt-1', 'subtask-list'); + subtaskList.classList.add('subtask-list'); if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach(subtask => { + task.subtasks.forEach((subtask) => { subtask.parentId = task.id; const subtaskIsChecked = checklistData.progress[subtask.id] || false; const subtaskItem = createChecklistItem(subtask, subtaskIsChecked, true); subtaskList.appendChild(subtaskItem); }); } - listItem.appendChild(subtaskList); + subtaskCollapsible.appendChild(subtaskList) + listItem.appendChild(subtaskCollapsible); + // On Click -> Collapse/Expand parentHeaderDiv.addEventListener('click', (e) => { if (e.target !== checkbox && !checkbox.contains(e.target) && !controlsContainer.contains(e.target) && !collapseIcon.contains(e.target) ) { const isExpanded = parentHeaderDiv.getAttribute('aria-expanded') === 'true'; parentHeaderDiv.setAttribute('aria-expanded', !isExpanded); - subtaskList.classList.toggle('collapsed', isExpanded); + subtaskCollapsible.classList.toggle('collapsed', isExpanded); } }); collapseIcon.addEventListener('click', (e) => { e.stopPropagation(); const isExpanded = parentHeaderDiv.getAttribute('aria-expanded') === 'true'; parentHeaderDiv.setAttribute('aria-expanded', !isExpanded); - subtaskList.classList.toggle('collapsed', isExpanded); + subtaskCollapsible.classList.toggle('collapsed', isExpanded); }); + // Checkbox Changed -> Change Subtasks Checkboxes checkbox.addEventListener('change', (event) => { - const currentlyChecked = event.target.checked; + let currentlyChecked; + if ("detail" in event) { + currentlyChecked = event.detail; + } else { + currentlyChecked = event.target.checked; + } + + event.target.checked = currentlyChecked; checklistData.progress[task.id] = currentlyChecked; - taskTextSpan.classList.toggle('checked', currentlyChecked); + taskText.classList.toggle('checked', currentlyChecked); - task.subtasks.forEach(subtask => { - checklistData.progress[subtask.id] = currentlyChecked; + task.subtasks.forEach((subtask) => { const subCheckbox = document.getElementById(subtask.id); - const subLabel = document.querySelector(`label[for="${subtask.id}"]`); - if (subCheckbox) subCheckbox.checked = currentlyChecked; - if (subLabel) subLabel.classList.toggle('checked', currentlyChecked); + + // this is kind of a stupid hack: We can recursively fire events with dispatchEvent, but those events + // don't know about the checkbox status of the originating event. So we attach that status to the + // `detail` of a CustomEvent, which takes priority over the local status. (Recursive firings like this + // allow for nested subtasks of arbitrary depth) + subCheckbox.dispatchEvent(new CustomEvent("change", {detail: currentlyChecked})); }); saveData(); }); } else { - if (isSubtask) { - listItem.classList.add('ml-4'); - } - const label = document.createElement('label'); label.htmlFor = task.id; - label.innerHTML = `
${task.text}
`; - - if (["location", "npc", "terminal", "prereq", "info"].some((prop) => task[prop])) { - if (task.npc && task.terminal) {console.warn(`Tasks should specify only one of [npc, terminal]. (${task.id})`);} - const infoLine = document.createElement('div'); - infoLine.classList.add('info-line'); - let infoLineHTML = ""; - infoLineHTML += makeInfoLineItem(task, "location", "Location", svgIcons.locationIcon); - infoLineHTML = infoLineHTML.replace('Base of Operations', '$&'); - infoLineHTML += makeInfoLineItem(task, "npc", "NPC", svgIcons.npcIcon); - infoLineHTML += makeInfoLineItem(task, "terminal", "Terminal", svgIcons.terminalIcon); - infoLineHTML += makeInfoLineItem(task, "prereq", "Requirements", svgIcons.prereqIcon); - infoLineHTML += makeInfoLineItem(task, "info", "Info", svgIcons.infoIcon); - infoLine.innerHTML = infoLineHTML; - - const infoLineExpander = document.createElement("div"); - infoLineExpander.classList.add("info-line-expander"); - infoLineExpander.appendChild(infoLine); - label.appendChild(infoLineExpander); + label.classList.add("task-description"); + if (isChecked) { label.classList.add("checked"); } + if (!isAvailable) { label.classList.add("unavailable"); } + + // Task Text + label.innerHTML = `${task.text}`; + + // Reset Timer + if (task.period) { + const resetTimer = document.createElement("span"); + resetTimer.classList.add("other-countdown"); + resetTimer.textContent = "(Loading...)"; + label.appendChild(resetTimer); } - label.classList.add('ml-2', 'flex-1', 'cursor-pointer'); - if (isChecked) { label.classList.add('checked'); } + // Info Line & Cycle Schedule + makeInfoLine(task, label); listItem.appendChild(checkbox); if (task.icon) { listItem.appendChild(icon); } listItem.appendChild(label); listItem.appendChild(controlsContainer); - checkbox.addEventListener('change', (event) => { - const currentlyChecked = event.target.checked; + // Checkbox Changed + checkbox.addEventListener("change", (event) => { + let currentlyChecked; + if ("detail" in event) { + currentlyChecked = event.detail; + } else { + currentlyChecked = event.target.checked; + } + + event.target.checked = currentlyChecked; checklistData.progress[task.id] = currentlyChecked; - label.classList.toggle('checked', currentlyChecked); + label.classList.toggle("checked", currentlyChecked); - if (isSubtask && task.parentId) { - let parentTaskDefinition = tasks.daily.find(t => t.id === task.parentId) || - tasks.weekly.find(t => t.id === task.parentId) || - tasks.other.find(t => t.id === task.parentId); + // Update parent task checkboxes + let t = task; + while (t.parentId) { // walk up the task tree + let parentTaskDefinition = getTaskById(t.parentId) if (parentTaskDefinition && parentTaskDefinition.subtasks) { - const allSubtasksChecked = parentTaskDefinition.subtasks.every(st => checklistData.progress[st.id]); + const allSubtasksChecked = parentTaskDefinition.subtasks.every((st) => checklistData.progress[st.id]); checklistData.progress[parentTaskDefinition.id] = allSubtasksChecked; const parentCheckbox = document.getElementById(parentTaskDefinition.id); - const parentContainer = parentCheckbox ? parentCheckbox.closest('.parent-task-container') : null; - const parentTextSpan = parentContainer ? parentContainer.querySelector('.parent-task-header .task-text') : null; + const parentContainer = parentCheckbox ? parentCheckbox.closest(".parent-task-container") : null; + const parentTextSpan = parentContainer ? parentContainer.querySelector(".parent-task-header .task-description") : null; - if (parentCheckbox) parentCheckbox.checked = allSubtasksChecked; - if (parentTextSpan) parentTextSpan.classList.toggle('checked', allSubtasksChecked); + if (parentCheckbox) {parentCheckbox.checked = allSubtasksChecked;} + if (parentTextSpan) {parentTextSpan.classList.toggle("checked", allSubtasksChecked);} } + + t = parentTaskDefinition; // move up a level } saveData(); }); @@ -735,22 +656,215 @@ function createChecklistItem(task, isChecked, isSubtask = false) { return listItem; } -function makeInfoLineItem(task, prop, iconToolTip, icon) { - if (task[prop]) { - return `${icon}${task[prop]}`; - } else { - return ""; +function taskDialogHeaderSetup(task, dialog) { + dialog.querySelector(":scope header .title").innerText = task.text.split(":")[0]; // take the task text up to the first ":" as the dialog title + + let taskIcon = dialog.querySelector(":scope .menu-title img.task-icon"); + taskIcon.className = "task-icon"; // remove possible `icon-filter` from previous opening + taskIcon.src = ""; + if (task.icon) { + taskIcon.src = iconURL(`tasks/${task.icon}`); + if (!task.noIconFilter) { + taskIcon.classList.add("icon-filter"); + } + } +} + +function showScheduleAction(task, period, cycleIndex, isAvailable) { + const cycleCount = cycles[task.id].columns[0].order.length; + + return () => { + taskDialogHeaderSetup(task, scheduleDialog); + + const thead = scheduleDialog.querySelector(":scope thead"); + const tbody = scheduleDialog.querySelector(":scope tbody"); + thead.innerHTML = ""; + tbody.innerHTML = ""; + + let header = `Date`; + for (const column of cycles[task.id].columns) { + header += `${column.name}`; + } + header += ""; + thead.innerHTML += header; + + const now = new Date(); + const ref = new Date(cycles[task.id].ref); + const cyclesSinceRef = Math.floor((now.getTime() - ref.getTime()) / period) + (isAvailable ? 0 : 1); // add 1 if unavailable to skip the current cycle for unavailable intermittent tasks + const currentCycleStartTime = ref.getTime() + (period * cyclesSinceRef); + + for (let i = 0; i <= cycleCount; i++) { + let date = "Now"; + if (i !== 0 || !isAvailable) { + const rowCycleStartDate = new Date(currentCycleStartTime + (period * i)); + date = rowCycleStartDate.toISOString().split("T")[0]; // toISOString is always in UTC, which is what we want. The part before "T" is the date + } + + let repeat = ""; + if (i === cycleCount) { + repeat = `(Cycle Repeats)`; + } + + let row = `${repeat}${date}`; // intermediate `row` variable is needed because manipulating `tbody.innerHTML` automatically adds `` tag + + for (const column of cycles[task.id].columns) { + const cellData = column.order[modulo(cycleIndex + i, cycleCount)]; + const align = column.align ? ` style="text-align: ${column.align}"` : ""; + row += `${makeCycleIcon(cellData)}${cellData.text}`; + } + + row += ""; + tbody.innerHTML += row; + } + scheduleDialog.showModal(); + } +} + +function showMoreInfoAction(task) { + return () => { + taskDialogHeaderSetup(task, moreInfoDialog); + document.getElementById("more-info-content").innerHTML = task.moreInfo; + moreInfoDialog.showModal(); + } +} + +function makeInfoLine(task, appendTo) { + const hasCycle = Object.hasOwn(cycles, task.id); + const hasInfoLine = ["location", "npc", "terminal", "prereq", "info", "moreInfo"].some((prop) => task[prop]); + + if (hasCycle || hasInfoLine) { + const taskInfoExpander = document.createElement("div"); + taskInfoExpander.classList.add("task-info-expander"); + const taskInfoExpanderContent = document.createElement("div"); + + // Cycle + if (hasCycle) { + const currentCycle = document.createElement("div"); + currentCycle.classList.add("current-cycle"); + + currentCycle.innerHTML += svgIcons.cycleIcon; + + const now = new Date(); + const ref = new Date(cycles[task.id].ref); + const cycleCount = cycles[task.id].columns[0].order.length; + + let prefix, period, cycleIndex; + const isAvailable = calcTaskTimes(task, now).isAvailable; + if (task.id.startsWith("weekly_")) { + prefix = "This Week"; + period = 7 * C.MILLISECONDS_PER_DAY; + if (ref.getUTCDay() !== 1) { + console.warn(`${task.id} cycle ref ${cycles[task.id].ref} is not a Monday`); + } + } + else if (task.id.startsWith("daily_")) { + prefix = "Today"; + period = C.MILLISECONDS_PER_DAY; + } + else { + prefix = "Current Cycle"; + if (!isAvailable) {prefix = "Next Cycle";} + period = parseDuration(task.period); + } + + cycleIndex = modulo(Math.floor((now.getTime() - ref.getTime()) / period), cycleCount); + if (!isAvailable) {cycleIndex++;} + console.log(`${task.id} cycleIndex ${cycleIndex}`); + const cycleData = cycles[task.id].columns[0].order[cycleIndex]; + + const cyclePrefix = document.createElement("span"); + cyclePrefix.innerHTML = `${prefix}: `; + currentCycle.appendChild(cyclePrefix); + + currentCycle.innerHTML += makeCycleIcon(cycleData); + + const cycleText = document.createElement("span"); + cycleText.textContent = cycleData.text; + currentCycle.appendChild(cycleText); + + const showSchedule = document.createElement("button"); + showSchedule.type = "button"; + showSchedule.classList.add("more-info-btn"); + showSchedule.innerHTML = "Show Schedule"; + showSchedule.addEventListener("click", showScheduleAction(task, period, cycleIndex, isAvailable)); + currentCycle.appendChild(showSchedule); + + taskInfoExpanderContent.appendChild(currentCycle); + } + + // Info Line + if (hasInfoLine) { + if (task.npc && task.terminal) {console.warn(`[${task.id}] Tasks should specify only one of [npc, terminal].`);} + const infoLine = document.createElement('div'); + infoLine.classList.add('info-line'); + let infoLineHTML = ""; + infoLineHTML += makeInfoLineItem(task, "location", "Location", svgIcons.locationIcon); + infoLineHTML = infoLineHTML.replace('Base of Operations', C.BASE_OF_OPERATIONS_TOOLTIP); + infoLineHTML += makeInfoLineItem(task, "npc", "NPC", svgIcons.npcIcon); + infoLineHTML += makeInfoLineItem(task, "terminal", "Terminal", svgIcons.terminalIcon); + infoLineHTML += makeInfoLineItem(task, "prereq", "Requirements", svgIcons.prereqIcon); + infoLineHTML += makeInfoLineItem(task, "info", "Info", svgIcons.infoIcon); + infoLine.innerHTML = infoLineHTML; + + if (task.moreInfo) { + const showMoreInfo = document.createElement("button"); + showMoreInfo.type = "button"; + showMoreInfo.classList.add("more-info-btn"); + showMoreInfo.innerHTML = "More Info"; + showMoreInfo.addEventListener("click", showMoreInfoAction(task)); + const span = document.createElement("span"); + span.appendChild(showMoreInfo); + infoLine.appendChild(span); + } + + taskInfoExpanderContent.appendChild(infoLine); + } + + taskInfoExpander.appendChild(taskInfoExpanderContent); + appendTo.appendChild(taskInfoExpander); + } +} + +function getTaskById(id) { + for (const group in tasks) { + for (const task of tasks[group]) { + if (task.id === id) { + return task; + } + const subtaskFound = _getSubtaskById(task, id); + if (subtaskFound) { + return subtaskFound; + } + } + } +} + +function _getSubtaskById(task, id) { + if ("subtasks" in task) { + for (const subtask of task.subtasks) { + if (subtask.id === id) { + return subtask; + } + const subtaskFound = _getSubtaskById(subtask, id); + if (subtaskFound) { + return subtaskFound; + } + } } + return undefined; } -function populateSection(sectionElement, taskList, progress) { +function populateSection(section) { + const sectionElement = document.querySelector(`#${section}-tasks-content ul`); + const taskList = tasks[section]; + if (!sectionElement) { console.error("Section element not found for population:", sectionElement); return; } sectionElement.innerHTML = ''; - taskList.forEach(task => { - const isChecked = progress[task.id] || false; + taskList.forEach((task) => { + const isChecked = checklistData.progress[task.id] || false; const listItem = createChecklistItem(task, isChecked); sectionElement.appendChild(listItem); }); @@ -760,7 +874,7 @@ function populateSection(sectionElement, taskList, progress) { } function resetSpecificButtonState(buttonElement, defaultText, stateKey) { - if (!buttonElement || !confirmState[stateKey]) return; + if (!buttonElement || !confirmState[stateKey]) { return }; clearTimeout(confirmState[stateKey].timeout); confirmState[stateKey].timeout = null; confirmState[stateKey].isConfirming = false; @@ -776,14 +890,14 @@ function handleResetConfirmation(buttonElement, confirmKey, defaultText, resetAc } if (!confirmState[confirmKey].isConfirming) { - Object.keys(confirmState).forEach(key => { + Object.keys(confirmState).forEach((key) => { if (key !== confirmKey && confirmState[key].isConfirming) { let btnElement, btnText; if (key === 'all') { btnElement = resetButton; btnText = 'Reset All Checks'; } else if (key === 'daily') { btnElement = resetDailyButton; btnText = 'Reset Daily Checks'; } else if (key === 'weekly') { btnElement = resetWeeklyButton; btnText = 'Reset Weekly Checks'; } else if (key === 'unhide') { btnElement = unhideTasksButton; btnText = 'Unhide All Tasks'; } - if(btnElement) { + if (btnElement) { resetSpecificButtonState(btnElement, btnText, key); } } @@ -816,72 +930,58 @@ function resetAllAction() { allCheckboxes.forEach((checkbox) => { checkbox.checked = false; const listItem = checkbox.closest('li'); - const label = listItem.querySelector('label'); - const parentTextSpan = listItem.querySelector('.parent-task-header .task-text'); - if (label) label.classList.remove('checked'); - if (parentTextSpan) parentTextSpan.classList.remove('checked'); + const taskDescription = listItem.querySelector(".task-description"); + if (taskDescription) {taskDescription.classList.remove("checked")}; }); updateLastSavedDisplay(checklistData.lastSaved); console.log("Checklist reset complete."); } -function resetSection(section) { +function resetSection(section, resetAltRefTasks=false) { const validSections = ["daily", "weekly"]; if (!validSections.includes(section)) { console.error("Section '%s' is not a valid section name: %s", section, validSections); return; } - let taskList, sectionElement, sectionElementId; - if (section === "daily") { - taskList = tasks.daily; - sectionElement = dailyList; - sectionElementId = 'daily-tasks-section'; - } else if (section === "weekly") { - taskList = tasks.weekly; - sectionElement = weeklyList; - sectionElementId = 'weekly-tasks-section'; - } - let didReset = false; - taskList.forEach(task => { - if (checklistData.progress[task.id] && !checklistData.hiddenTasks[task.id]) { + + let didReset = 0; + + function resetTask(task) { + if (task.ref && !resetAltRefTasks) { + didReset += otherTaskReset(task); + } else if (checklistData.progress[task.id] && !checklistData.hiddenTasks[task.id]) { checklistData.progress[task.id] = false; - didReset = true; + didReset++; } - if (task.isParent && task.subtasks) { - task.subtasks.forEach(subtask => { - if (checklistData.progress[subtask.id] && !checklistData.hiddenTasks[subtask.id]) { - checklistData.progress[subtask.id] = false; - didReset = true; - } - }); - } - }); + if (task.subtasks) {task.subtasks.forEach(resetTask);} + } + tasks[section].forEach(resetTask); + const now = new Date().toISOString(); if (section === "daily") {checklistData.lastDailyReset = now;} else if (section === "weekly") {checklistData.lastWeeklyReset = now;} saveData(); if (didReset) { - populateSection(sectionElement, taskList, checklistData.progress); - updateSectionControls(sectionElementId); + populateSection(section); } - console.log("%s checks reset.", section); + console.log(`${section} checks reset.`); } function resetDailyAction() { - resetSection("daily"); + resetSection("daily", true); } function resetWeeklyAction() { - resetSection("weekly"); + resetSection("weekly", true); } function handleSectionToggle(event) { const header = event.target.closest('.section-toggle'); - if (!header) return; + if (!header) { return; } const contentId = header.getAttribute('aria-controls'); const contentDiv = document.getElementById(contentId); - if (!contentDiv) return; + if (!contentDiv) { return; } const isExpanded = header.getAttribute('aria-expanded') === 'true'; header.setAttribute('aria-expanded', !isExpanded); @@ -889,34 +989,25 @@ function handleSectionToggle(event) { console.log(`Toggled section ${contentId} to ${!isExpanded ? 'expanded' : 'collapsed'}`); } -function toggleMenu() { - if (slideoutMenuOverlay) { - slideoutMenuOverlay.classList.toggle('open'); - } -} - function unhideAllAction() { checklistData.hiddenTasks = {}; checklistData.manuallyHiddenSections = {}; saveData(false); - document.querySelectorAll('.task-item.hidden-task').forEach(item => item.classList.remove('hidden-task')); - document.querySelectorAll('section.section-is-hidden-by-user').forEach(section => section.classList.remove('section-is-hidden-by-user')); + document.querySelectorAll(".task-item.hidden-task").forEach((item) => item.classList.remove("hidden-task")); + document.querySelectorAll("section.section-is-hidden-by-user").forEach((section) => section.classList.remove("section-is-hidden-by-user")); - populateSection(dailyList, tasks.daily, checklistData.progress); - populateSection(weeklyList, tasks.weekly, checklistData.progress); - populateSection(otherList, tasks.other, checklistData.progress); - ['daily-tasks-section', 'weekly-tasks-section', 'other-tasks-section'].forEach(updateSectionControls); + ["daily", "weekly", "other"].forEach(populateSection); console.log("All tasks and sections unhidden."); - toggleMenu(); + optionsMenu.close(); } function updateSectionControls(sectionElementId) { const sectionElement = document.getElementById(sectionElementId); - if (!sectionElement) return; + if (!sectionElement) { return; } const hideSectionButton = sectionElement.querySelector('.hide-section-button'); - if (!hideSectionButton) return; + if (!hideSectionButton) { return; } if (checklistData.manuallyHiddenSections[sectionElementId]) { sectionElement.classList.add('section-is-hidden-by-user'); @@ -938,7 +1029,7 @@ function updateSectionControls(sectionElementId) { return; } - const allTasksIndividuallyHidden = allTaskItems.every(item => item.classList.contains('hidden-task')); + const allTasksIndividuallyHidden = allTaskItems.every((item) => item.classList.contains("hidden-task")); if (allTasksIndividuallyHidden) { hideSectionButton.classList.add('visible'); @@ -947,11 +1038,33 @@ function updateSectionControls(sectionElementId) { } } -function loadAndInitializeApp() { - initializeDOMElements(); +export function stopCountdown() { + if (countdownInterval) { clearInterval(countdownInterval); } +} + +export function startCountdown() { + stopCountdown(); + countdownInterval = setInterval(handleResets, 1000); +} + +function saveData(showStatus = true) { hideError(); - loadThemePreference(); + checklistData.lastSaved = new Date().toISOString(); + try { + localStorage.setItem(DATA_STORAGE_KEY, JSON.stringify(checklistData)); + updateLastSavedDisplay(checklistData.lastSaved); + if (showStatus) { showSaveStatus(); } + } catch (e) { + console.error("Error saving data to localStorage:", e); + let userMessage = "Could not save progress."; + if (e.name === 'QuotaExceededError' || (e.code && (e.code === 22 || e.code === 1014))) { + userMessage = "Could not save progress. Browser storage might be full."; + } + displayError(userMessage); + } +} +function loadData() { const savedData = localStorage.getItem(DATA_STORAGE_KEY); if (savedData) { try { @@ -963,41 +1076,41 @@ function loadAndInitializeApp() { checklistData.lastWeeklyReset = parsedData.lastWeeklyReset || null; checklistData.hiddenTasks = parsedData.hiddenTasks || {}; checklistData.manuallyHiddenSections = parsedData.manuallyHiddenSections || {}; - checklistData.lastEightHourResets = parsedData.lastEightHourResets || {}; + checklistData.lastTaskResetTimes = parsedData.lastTaskResetTimes || {}; checklistData.notificationPreferences = parsedData.notificationPreferences || {}; checklistData.notificationsSent = parsedData.notificationsSent || {}; } else { console.warn("Invalid data format found in localStorage. Starting fresh."); } } catch (e) { console.error("Error parsing saved data:", e); displayError("Failed to load saved progress. Data might be corrupted."); - checklistData = { progress: {}, lastSaved: null, lastDailyReset: null, lastWeeklyReset: null, hiddenTasks: {}, manuallyHiddenSections: {}, lastEightHourResets: {}, notificationPreferences: {}, notificationsSent: {} }; + checklistData = { progress: {}, lastSaved: null, lastDailyReset: null, lastWeeklyReset: null, hiddenTasks: {}, manuallyHiddenSections: {}, lastTaskResetTimes: {}, notificationPreferences: {}, notificationsSent: {} }; } } +} +export function loadAndInitializeApp() { + initializeDOMElements(); + hideError(); + loadThemePreference(); + loadData(); setDailyBackground(); - displayLocalResetTimes(); - if (countdownInterval) clearInterval(countdownInterval); - countdownInterval = setInterval(displayLocalResetTimes, 1000); - - populateSection(dailyList, tasks.daily, checklistData.progress); - populateSection(weeklyList, tasks.weekly, checklistData.progress); - populateSection(otherList, tasks.other, checklistData.progress); - updateLastSavedDisplay(checklistData.lastSaved); - ['daily-tasks-section', 'weekly-tasks-section', 'other-tasks-section'].forEach(updateSectionControls); + handleResets(); + ["daily", "weekly", "other"].forEach(populateSection); + updateLastSavedDisplay(checklistData.lastSaved); // Setup event listeners - if (appVersionElement) appVersionElement.textContent = APP_VERSION; - else console.error("App version element not found!"); + if (appVersionElement) { appVersionElement.textContent = APP_VERSION; } + else { console.error("App version element not found!"); } if (gitHashElement) { gitHashElement.textContent = GIT_COMMIT_HASH; gitHashElement.href = `https://github.com/warframe-tools/Task-Checklist/tree/${GIT_COMMIT_HASH_LONG}`; - } else console.error("git hash element not found!"); + } else { console.error("git hash element not found!"); } - if (wfVersionElement) wfVersionElement.textContent = `Warframe Version ${WARFRAME_VERSION}`; - else console.error("Warframe version element not found!"); + if (wfVersionElement) { wfVersionElement.textContent = `Warframe Version ${WARFRAME_VERSION}`; } + else { console.error("Warframe version element not found!"); } if (resetDailyButton) { resetDailyButton.addEventListener('click', () => handleResetConfirmation(resetDailyButton, 'daily', 'Reset Daily Checks', resetDailyAction)); } else { console.error("Reset Daily button element not found!"); } @@ -1015,41 +1128,32 @@ function loadAndInitializeApp() { if (themeToggleButton) { themeToggleButton.addEventListener('click', handleThemeToggle); } else { console.error("Theme toggle button not found!"); } - if (hamburgerButton) { hamburgerButton.addEventListener('click', toggleMenu); } + if (hamburgerButton) { hamburgerButton.addEventListener('click', () => {optionsMenu.showModal();}); } else { console.error("Hamburger button not found!"); } - if (slideoutMenuOverlay) { - slideoutMenuOverlay.addEventListener('click', function(event) { - if (event.target === slideoutMenuOverlay) { - toggleMenu(); - } - }); - } else { console.error("Slideout menu overlay not found!"); } - - if (menuCloseButton) { - menuCloseButton.addEventListener('click', toggleMenu); - } else { console.error("Menu close button not found!"); } - - - sectionToggles.forEach(toggle => { - toggle.addEventListener('click', handleSectionToggle); + sectionToggles.forEach((toggle) => { + toggle.addEventListener("click", handleSectionToggle); }); - document.getElementById('checklist-container').addEventListener('click', function(event) { - if (event.target.classList.contains('hide-section-button')) { - const sectionId = event.target.dataset.sectionId; - if (sectionId) { - const sectionElement = document.getElementById(sectionId); - if (sectionElement) { - checklistData.manuallyHiddenSections[sectionId] = true; - sectionElement.classList.add('section-is-hidden-by-user'); - event.target.classList.remove('visible'); - saveData(false); - console.log(`Section ${sectionId} manually hidden.`); + try { + document.getElementById('checklist-container').addEventListener('click', function(event) { + if (event.target.classList.contains('hide-section-button')) { + const sectionId = event.target.dataset.sectionId; + if (sectionId) { + const sectionElement = document.getElementById(sectionId); + if (sectionElement) { + checklistData.manuallyHiddenSections[sectionId] = true; + sectionElement.classList.add('section-is-hidden-by-user'); + event.target.classList.remove('visible'); + saveData(false); + console.log(`Section ${sectionId} manually hidden.`); + } } } - } - }); + }); + } catch (e) { + console.error("Checklist container not found!"); + } if (errorCloseButton) { @@ -1060,20 +1164,38 @@ function loadAndInitializeApp() { errorCopyButton.addEventListener('click', copyErrorToClipboard); } else { console.error("Error copy button not found!"); } + for (const dialog of document.getElementsByTagName("dialog")) { + // clicking inside the dialog's top-level div does nothing + dialog.querySelector(":scope > div").addEventListener("click", (e) => { + e.stopPropagation(); + }); + + // clicking the close button... + dialog.querySelector(":scope .menu-close-button").addEventListener("click", () => { + dialog.close(); + }); + + // clicking anywhere else on the dialog (i.e., the anywhere else the page (the ::backdrop)) will close it + dialog.addEventListener("click", () => { + dialog.close(); + }); + } + console.log(`Warframe Checklist App Initialized (v${APP_VERSION} (${GIT_COMMIT_HASH})) from app.js.`); } // --- Initialization --- -document.addEventListener('DOMContentLoaded', () => { +document.addEventListener("DOMContentLoaded", () => { try { loadAndInitializeApp(); + startCountdown(); } catch(error) { console.error("Critical Error during app.js initialization:", error); - const errDisp = document.getElementById('error-display'); - const errMsg = document.getElementById('error-message'); - if(errDisp && errMsg) { + const errDisp = document.getElementById("error-display"); + const errMsg = document.getElementById("error-message"); + if (errDisp && errMsg) { errMsg.textContent = "A critical error occurred during application startup. Please check the console."; - errDisp.classList.add('visible'); + errDisp.classList.add("visible"); } else { alert("A critical error occurred during application startup. Please check the console."); } diff --git a/sources/js/constants.js b/sources/js/constants.js new file mode 100644 index 0000000..c1ca53f --- /dev/null +++ b/sources/js/constants.js @@ -0,0 +1,9 @@ +// --- sources/js/constants.js --- +export const MILLISECONDS_PER_SECOND = 1000; +export const MILLISECONDS_PER_MINUTE = 60 * MILLISECONDS_PER_SECOND; +export const MILLISECONDS_PER_HOUR = 60 * MILLISECONDS_PER_MINUTE; +export const MILLISECONDS_PER_DAY = 24 * MILLISECONDS_PER_HOUR; + +export const SERVER_TIMEZONE = "America/Toronto"; // used for determining Daylight Saving Time + +export const BASE_OF_OPERATIONS_TOOLTIP = 'Base of Operations'; diff --git a/sources/js/cycles.json b/sources/js/cycles.json new file mode 100644 index 0000000..98d9aa8 --- /dev/null +++ b/sources/js/cycles.json @@ -0,0 +1,214 @@ +{ + "weekly_ayatan": { + "ref": "2026-01-26", + "columns": [ + { + "name": "Item", + "order": [ + { "text": "Ayatan Sah Sculpture", "icon": "OroFusexA.png" }, + { "text": "Ayatan Ayr Sculpture", "icon": "OroFusexB.png" }, + { "text": "Ayatan Orta Sculpture", "icon": "OroFusexC.png" }, + { "text": "Ayatan Vaya Sculpture", "icon": "OroFusexD.png" }, + { "text": "Ayatan Piv Sculpture", "icon": "OroFusexE.png" }, + { "text": "Ayatan Valana Sculpture", "icon": "OroFusexG.png" } + ] + }, + { + "name": "Tileset", + "order": [ + { "text": "Orokin Derelict (Deimos)", "icon": "FactionInfested.png" }, + { "text": "Orokin Tower (Void)", "icon": "FactionOrokin.png" }, + { "text": "Orokin Derelict (Deimos)", "icon": "FactionInfested.png" }, + { "text": "Orokin Tower (Void)", "icon": "FactionOrokin.png" }, + { "text": "Orokin Derelict (Deimos)", "icon": "FactionInfested.png" }, + { "text": "Orokin Tower (Void)", "icon": "FactionOrokin.png" } + ] + } + ] + }, + "weekly_kahl_garrison": { + "ref": "2026-01-12", + "columns": [ + { + "name": "Mission", + "order": [ + { "text": "Sneaky Sabotage", "icon": "FactionCorpus.png" }, + { "text": "Junk Run", "icon": "FactionGrineer.png" }, + { "text": "Prison Break", "icon": "SentientSymbol.png" } + ] + }, + { + "name": "Tileset", + "order": [ + { "text": "Spaceport (Orb Vallis, Venus)" }, + { "text": "Grineer Forest (Earth)" }, + { "text": "Murex" } + ] + } + ] + }, + "weekly_archon_hunt": { + "ref": "2026-01-12", + "columns": [ + { + "name": "Item", + "order": [ + { "text": "Azure Archon Shard", "icon": "ArchonShardBoreal.png" }, + { "text": "Crimson Archon Shard", "icon": "ArchonShardAmar.png" }, + { "text": "Amber Archon Shard", "icon": "ArchonShardNira.png" } + ] + }, + { + "name": "Boss", + "align": "left", + "order": [ + { "text": "🦉 Archon Boreal" }, + { "text": "🐺 Archon Amar" }, + { "text": "🐍 Archon Nira" } + ] + }, + { + "name": "Tileset", + "order": [ + { "text": "Grineer Forest (Earth)", "icon": "FactionGrineer.png" }, + { "text": "Grineer Settlement (Mars)", "icon": "FactionGrineer.png" }, + { "text": "Corpus Gas City (Jupiter)", "icon": "FactionAmalgam.png" } + ] + } + ] + }, + "weekly_duviri_circuit": { + "ref": "2026-01-26", + "columns": [ + { + "name": "Items", + "order": [ + { "text": "Blueprints for Excalibur, Trinity, or Ember", "icon": "IconWarframe_256.png", "iconFilter": true }, + { "text": "Blueprints for Loki, Mag, or Rhino", "icon": "IconWarframe_256.png", "iconFilter": true }, + { "text": "Blueprints for Ash, Frost, or Nyx", "icon": "IconWarframe_256.png", "iconFilter": true }, + { "text": "Blueprints for Saryn, Vauban, or Nova", "icon": "IconWarframe_256.png", "iconFilter": true }, + { "text": "Blueprints for Nekros, Valkyr, or Oberon", "icon": "IconWarframe_256.png", "iconFilter": true }, + { "text": "Blueprints for Hydroid, Mirage, or Limbo", "icon": "IconWarframe_256.png", "iconFilter": true }, + { "text": "Blueprints for Mesa, Chroma, or Atlas", "icon": "IconWarframe_256.png", "iconFilter": true }, + { "text": "Blueprints for Ivara, Inaros, or Titania", "icon": "IconWarframe_256.png", "iconFilter": true }, + { "text": "Blueprints for Nidus, Octavia, or Harrow", "icon": "IconWarframe_256.png", "iconFilter": true }, + { "text": "Blueprints for Gara, Khora, or Revenant", "icon": "IconWarframe_256.png", "iconFilter": true }, + { "text": "Blueprints for Garuda, Baruuk, or Hildryn", "icon": "IconWarframe_256.png", "iconFilter": true } + ] + } + ] + }, + "weekly_duviri_circuit_sp": { + "ref": "2026-01-05", + "columns": [ + { + "name": "Items", + "order": [ + { "text": "Incarnon Adapters for Braton, Kunai, Lato, Paris, and Skana", "icon": "IncarnonEvolveIconSmall.png", "iconFilter": true }, + { "text": "Incarnon Adapters for Angstrum, Anku, Boar, Gammacor, and Gorgon", "icon": "IncarnonEvolveIconSmall.png", "iconFilter": true }, + { "text": "Incarnon Adapters for Bo, Furax, Furis, Latron, and Strun", "icon": "IncarnonEvolveIconSmall.png", "iconFilter": true }, + { "text": "Incarnon Adapters for Boltor, Bronco, Ceramic Dagger, Lex, and Magistar", "icon": "IncarnonEvolveIconSmall.png", "iconFilter": true }, + { "text": "Incarnon Adapters for Atomos, Dual Ichor, Dual Toxocyst, Miter, and Torid", "icon": "IncarnonEvolveIconSmall.png", "iconFilter": true }, + { "text": "Incarnon Adapters for Ack & Brunt, Burston, Nami Solo, Soma, and Vasto", "icon": "IncarnonEvolveIconSmall.png", "iconFilter": true }, + { "text": "Incarnon Adapters for Despair, Dread, Hate, Sibear, and Zylok", "icon": "IncarnonEvolveIconSmall.png", "iconFilter": true }, + { "text": "Incarnon Adapters for Cestra, Dera, Okina, Sicarus, and Sybaris", "icon": "IncarnonEvolveIconSmall.png", "iconFilter": true } + ] + } + ] + }, + "weekly_calendar" : { + "ref": "2024-12-16", + "columns": [ + { + "name": "Season", + "order": [ + { "text": "Winter", "icon": "RetroSeasonalWinterIcon_.png" }, + { "text": "Spring", "icon": "RetroSeasonalSpringIcon_.png" }, + { "text": "Summer", "icon": "RetroSeasonalSummerIcon_.png" }, + { "text": "Autumn", "icon": "RetroSeasonalAutumnIcon_.png" } + ] + }, + { + "name": "Increased Spawns", + "align": "left", + "order": [ + { "text": "❄️ Arctic Eximus" }, + { "text": "🟢 Jade Light Eximus" }, + { "text": "🔥 Arson Eximus" }, + { "text": "🧲 Energy Leech Eximus" } + ] + } + ] + }, + "weekly_teshin": { + "ref": "2026-02-02", + "columns": [ + { + "name": "Item", + "order": [ + { "text": "Umbra Forma Blueprint", "icon": "FormaUmbra.png" }, + { "text": "50,000 Kuva", "icon": "Kuva.png" }, + { "text": "Kitgun Riven Mod", "icon": "OmegaModIconRendered256.png" }, + { "text": "3x Built Forma", "icon": "FormaPack.png" }, + { "text": "Zaw Riven Mod", "icon": "OmegaModIconRendered256.png" }, + { "text": "30,000 Endo", "icon": "EndoIconRenderLarge.png" }, + { "text": "Rifle Riven Mod", "icon": "OmegaModIconRendered256.png" }, + { "text": "Shotgun Riven Mod", "icon": "OmegaModIconRendered256.png" } + ] + }, + { + "name": "Price", + "order": [ + { "text": "150", "icon": "SteelEssence.png" }, + { "text": "55", "icon": "SteelEssence.png" }, + { "text": "75", "icon": "SteelEssence.png" }, + { "text": "75", "icon": "SteelEssence.png" }, + { "text": "75", "icon": "SteelEssence.png" }, + { "text": "150", "icon": "SteelEssence.png" }, + { "text": "75", "icon": "SteelEssence.png" }, + { "text": "75", "icon": "SteelEssence.png" } + ] + } + ] + }, + "weekly_bird3": { + "ref": "2026-01-12", + "columns": [ + { + "name": "Item", + "order": [ + { "text": "Azure Archon Shard", "icon": "ArchonShardBoreal.png" }, + { "text": "Amber Archon Shard", "icon": "ArchonShardNira.png" }, + { "text": "Crimson Archon Shard", "icon": "ArchonShardAmar.png" } + ] + } + ] + }, + "other_baro": { + "ref": "2022-12-30T09:00:00-05:00", + "columns": [ + { + "name": "Location", + "align": "left", + "order": [ + { "text": "Strata Relay, Earth" }, + { "text": "Larunda Relay, Mercury" }, + { "text": "Kronia Relay, Saturn" }, + { "text": "Orcus Relay, Pluto (MR 8+)" } + ] + } + ] + }, + "other_eleanor": { + "ref": "2025-03-18", + "columns": [ + { + "name": "Coda Weapons", + "order": [ + { "text": "Hema, Sporothrix, Catabolyst, Pox, Dual Torxica, Mire, and Motovore" }, + { "text": "Bassocyst, Bubonico, Synapse, Tysis, Caustacyst, Hirudo, and Pathocyst" } + ] + } + ] + } +} diff --git a/sources/js/functions.js b/sources/js/functions.js new file mode 100644 index 0000000..a5476b6 --- /dev/null +++ b/sources/js/functions.js @@ -0,0 +1,180 @@ +// --- sources/js/functions.js --- +/** Functions that don't manipulate or depend on the DOM or global variables, + * and have no side effects (apart from console messages). + * Functions that generate HTML or DOM elements are allowed here, + * as long as they don't actually insert them into the document + */ + +import * as C from "./constants.js"; + +export function modulo(n, d) { + return ((n % d) + d) % d; +} + +const taskIcons = import.meta.glob("../img/icons/**/*.png", {eager: true, query: '?url', import: 'default'}); +export function iconURL(iconName) { + return taskIcons["../img/icons/" + iconName]; +} + +export function makeCycleIcon(cycleData) { + if (cycleData.icon) { + const src = iconURL(`cycles/${cycleData.icon}`) + let classList = "cycle-icon"; + if (cycleData.iconFilter) { + classList += " icon-filter"; + } + return ``; + } else { + return ""; + } +} + +export function formatTimestamp(timestamp) { + if (!timestamp) return 'Never'; + try { + const date = new Date(timestamp); + if (isNaN(date.getTime())) return 'Invalid Date'; + return date.toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' }); + } catch (e) { console.error("Error formatting timestamp:", e); return 'Error'; } +} + +export function getMostRecentMondayMidnightUTC() { + const now = new Date(); + const currentUTCDay = now.getUTCDay(); + const daysSinceMondayUTC = (currentUTCDay === 0) ? 6 : currentUTCDay - 1; + + const mondayUTC = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())); + mondayUTC.setUTCDate(mondayUTC.getUTCDate() - daysSinceMondayUTC); + mondayUTC.setUTCHours(0, 0, 0, 0); + return mondayUTC.getTime(); +} + +export function getNextDailyMidnightUTC() { + const now = new Date(); + const tomorrowUTC = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1)); + tomorrowUTC.setUTCHours(0, 0, 0, 0); + return tomorrowUTC.getTime(); +} + +export function getUTCDateString(dateObj) { + const year = dateObj.getUTCFullYear(); + const month = (dateObj.getUTCMonth() + 1).toString().padStart(2, '0'); + const day = dateObj.getUTCDate().toString().padStart(2, '0'); + return `${year}-${month}-${day}`; +} + +export function getUTCDayOfYear(date) { + const startOfYear = Date.UTC(date.getUTCFullYear(), 0, 0); + const diff = date.getTime() - startOfYear; + return Math.floor(diff / C.MILLISECONDS_PER_DAY); +} + +export function formatCountdown(ms) { + if (ms <= 0) { return "00:00:00"; } + + const days = Math.floor(ms / C.MILLISECONDS_PER_DAY); + ms %= C.MILLISECONDS_PER_DAY; + const hours = Math.floor(ms / C.MILLISECONDS_PER_HOUR); + ms %= C.MILLISECONDS_PER_HOUR; + const minutes = Math.floor(ms / C.MILLISECONDS_PER_MINUTE); + ms %= C.MILLISECONDS_PER_MINUTE; + const seconds = Math.floor(ms / C.MILLISECONDS_PER_SECOND); + + const pad = (num) => String(num).padStart(2, '0'); + + if (days > 0) { + return `${days}d ${pad(hours)}:${pad(minutes)}:${pad(seconds)}`; + } + return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`; +} + +/** Returns the duration in milliseconds of the given "duration string". + * Duration strings look like "14d 5h 20m 15s", for number of days, hours + * minutes, and seconds. All parts are optional. Spaces are optional. + * Case insensitive. Integers only. + */ +export function parseDuration(str) { + if (!str || typeof str !== "string") {return undefined;} + + const map = { + "d": C.MILLISECONDS_PER_DAY, + "h": C.MILLISECONDS_PER_HOUR, + "m": C.MILLISECONDS_PER_MINUTE, + "s": C.MILLISECONDS_PER_SECOND + }; + return str.toLowerCase().matchAll(/((\d+)([a-z]))/g).toArray().reduce((acc, curr) => { + const n = parseInt(curr[2]); + let mult; + if (Object.hasOwn(map, curr[3])) { + mult = map[curr[3]]; + } else { + console.warn(`unknown time multiplier '${curr[3]}'. Skipping "${curr[1]}" in "${str}"`); + mult = 0; + } + return acc + (n * mult); + }, 0); +} + +/** returns whether the given date is in Daylight Saving Time in the named timezone */ +export function isDst(date, timezone) { + if (typeof date === "undefined" || Number.isNaN(date)) {return undefined;} + if (typeof date === "number" || typeof date === "string") {date = new Date(date);} + + const dateFormat = Intl.DateTimeFormat("en-CA", {timeZone: timezone, timeZoneName: "shortOffset"}); + + function wholeHourOffset(d) { + // returns the whole hour part of the UTC offset as an integer. + // note the existence of fractional timezones (India, Central Australia, Newfoundland, etc.) + const timeZoneName = dateFormat.formatToParts(d).find((p) => p.type === "timeZoneName").value; + return parseInt(timeZoneName.replace("GMT", "").split(":")[0], 10); + } + + const year = date.getUTCFullYear(); + const janOffset = wholeHourOffset(new Date(Date.UTC(year, 0, 1))); + const julOffset = wholeHourOffset(new Date(Date.UTC(year, 6, 1))); + if (janOffset === julOffset) {return false;} + const currentOffset = wholeHourOffset(date); + const dstOffset = Math.max(janOffset, julOffset); + return currentOffset === dstOffset; +} + +/** calculates the cycleNumber (number of resets since the reference time) of the given task at the given time (a Date object or timestamp) */ +export function calcCycleNumber(task, time) { + time = new Date(time); + const ref = new Date(task.ref || 0); + const period = parseDuration(task.period); + let diff = time.getTime() - ref.getTime(); + if (task.observesDst && isDst(time, C.SERVER_TIMEZONE)) { + diff += C.MILLISECONDS_PER_HOUR; + } + return Math.floor(diff / period); +} + +export function makeInfoLineItem(task, prop, iconToolTip, icon) { + if (task[prop]) { + return `${icon}${task[prop]}`; + } else { + return ""; + } +} + +/** Calculate the next task time(s) from the given task at the given date */ +export function calcTaskTimes(task, date) { + date = new Date(date); + const ref = new Date(task.ref || 0); + const period = parseDuration(task.period); + const cycleNumber = calcCycleNumber(task, date); + const prevResetTimestamp = ref.getTime() + (cycleNumber * period); + let nextResetTimestamp = prevResetTimestamp + period; + let thisCycleLeaveTimestamp = prevResetTimestamp + parseDuration(task.duration); + + if (task.observesDst) { + if (isDst(nextResetTimestamp, C.SERVER_TIMEZONE)) {nextResetTimestamp -= C.MILLISECONDS_PER_HOUR;} + if (isDst(thisCycleLeaveTimestamp, C.SERVER_TIMEZONE)) {thisCycleLeaveTimestamp -= C.MILLISECONDS_PER_HOUR;} + } + + let isAvailable = true; + if (task.duration) {isAvailable = (date.getTime() < thisCycleLeaveTimestamp);} + + return {nextResetTimestamp, thisCycleLeaveTimestamp, isAvailable}; +} diff --git a/sources/js/icons.js b/sources/js/icons.js index 29392e3..deb7ba7 100644 --- a/sources/js/icons.js +++ b/sources/js/icons.js @@ -1,12 +1,12 @@ // Icons from https://heroicons.com/ -const chevronDownOutline = ` - +const chevronDownOutline = +` `; -const eyeSlashOutline = ` - +const eyeSlashOutline = +` +const bellOutline = +` +const mapPin16 = +` +const user24 = +` `; -const computerDesktop16 = ` - +const computerDesktop16 = +` +const clipboardDocumentCheck16 = +` +const informationCircle16 = +` `; +const calendarDays16 = +` + + +` export { chevronDownOutline as collapseIcon, @@ -86,4 +99,5 @@ export { computerDesktop16 as terminalIcon, clipboardDocumentCheck16 as prereqIcon, informationCircle16 as infoIcon, + calendarDays16 as cycleIcon, }; diff --git a/sources/js/moreInfo.js b/sources/js/moreInfo.js new file mode 100644 index 0000000..1fedfe1 --- /dev/null +++ b/sources/js/moreInfo.js @@ -0,0 +1,72 @@ +/** Storing this in tasks.json is unwieldy, so it lives here. + * If a task's id appears in the exported object, that value gets attached to the task as the `moreInfo` property by `_prepTasks()`. + */ + +import * as C from "./constants.js"; +import { iconURL } from "./functions.js"; + +function _factionIcon(name) { + const src = iconURL(`tasks/syndicates/${name}`); + return ``; +} +const factions = `

The Faction Syndicates are + ${_factionIcon("FactionSigilRebels.png")}Steel Meridian, + ${_factionIcon("FactionSigilJudge.png")}Arbiters of Hexis, + ${_factionIcon("FactionSigilOracle.png")}Cephalon Suda, + ${_factionIcon("FactionSigilBusiness.png")}The Perrin Sequence, + ${_factionIcon("FactionSigilAssassins.png")}Red Veil, + and ${_factionIcon("FactionSigilChurch.png")}New Loka.

+

Pledge your loyalty to a faction syndicate at the Syndicates console in your ${C.BASE_OF_OPERATIONS_TOOLTIP}.

` + +export default { + daily_first_win_bonus: `

Your first completed mission after the daily reset gives double base credit rewards. This only applies to the end-of-mission bonus, and does not apply to credits picked up in the mission.

+

It also does not apply to the following mission types and locations:

+ +

Completing any of these missions will consume the bonus without awarding extra credits.

+

The bonus does stack with Credit Boosters, Credit Blessings, and double Credit events. Travelling to a relay or other non-mission area does not consume the bonus.

+

Some good missions to use it on include:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeBase CreditsEnemy Level
Tikal, EarthDark Sector Excavation13,5006 - 16
Gabii, CeresDark Sector Survival22,40015 - 25
Bendar Cluster, Earth ProximaRailjack Skirmish48,80029 - 36
Sabmir Cloud, Veil ProximaRailjack Spy156,60056 - 60
+

See Daily First Win Bonus, Dark Sectors, and Mission on the wiki for more details.

+

(There's currently a visual bug where the bonus credits don't appear on the mission completion screen. They are still added to your account though.)

`, + + daily_syndicate_gain: factions, + daily_syndicate_spend: factions +} diff --git a/sources/js/tasks.json b/sources/js/tasks.json index c38272a..266ba66 100644 --- a/sources/js/tasks.json +++ b/sources/js/tasks.json @@ -19,10 +19,18 @@ "location": "Base of Operations", "terminal": "Foundry" }, + { + "id": "daily_first_win_bonus", + "text": "Daily First Win Bonus: Get double base credit reward on your first mission.", + "icon": "DoubleCreditEvent.png", + "location": "Base of Operations", + "terminal": "Navigation" + }, { "id": "daily_syndicate_gain", "text": "Faction Syndicates: Gain daily standing cap with your pledged Syndicate(s).", "icon": "ReputationSmall.png", + "prereq": "Mastery Rank 3", "info": "Gain affinity in any mission to increase faction standing" }, { @@ -30,13 +38,13 @@ "text": "Faction Syndicates: If maxed on standing, spend it (Relic packs, Vosfor packs, etc.).", "icon": "ReputationSmall.png", "location": "Base of Operations/Any Relay", - "terminal": "Syndicates" + "terminal": "Syndicates", + "prereq": "Mastery Rank 3" }, { "id": "daily_world_syndicate_parent", "text": "World Syndicates (Standing)", "icon": "ReputationSmall.png", - "isParent": true, "subtasks": [ { "id": "daily_world_syndicate_simaris", @@ -116,20 +124,15 @@ } ] }, - { - "id": "daily_dark_sector", - "text": "Dark Sector Mission (Early Game): Complete one Dark Sector mission first for double credits (if needed & pre-Index).", - "icon": "IconInfested.png", - "location": "Base of Operations", - "terminal": "Navigation" - }, { "id": "daily_sortie", "text": "Sortie: Complete the 3 daily Sortie missions.", "icon": "Sortie.png", "location": "Base of Operations", "terminal": "Navigation", - "prereq": "The War Within" + "prereq": "The War Within", + "ref": "2015-12-03T12:00:00-05:00", + "observesDst": true }, { "id": "daily_focus", @@ -143,18 +146,130 @@ "icon": "SteelEssenceIcon.png", "prereq": "Steel Path unlocked" }, + { + "id": "daily_kim_parent", + "text": "KIM: complete daily chats with protoframes", + "location": "Base of Operations", + "terminal": "POM-2 PC", + "icon": "Computer.png", + "subtasks": [ + { + "id": "daily_kim_hex_parent", + "text": "The Hex", + "prereq": "The Hex (Quest)", + "icon": "KIM/RetroPfpG.png", + "noIconFilter": true, + "subtasks": [ + { + "id": "daily_kim_arthur", + "text": "Broadsword (Arthur)", + "icon": "KIM/RetroPfpA.png", + "noIconFilter": true + }, + { + "id": "daily_kim_eleanor", + "text": "Salem (Eleanor)", + "icon": "KIM/RetroPfpE.png", + "noIconFilter": true + }, + { + "id": "daily_kim_lettie", + "text": "Belladona ~{@ (Lettie)", + "icon": "KIM/RetroPfpB.png", + "noIconFilter": true + }, + { + "id": "daily_kim_amir", + "text": "H16h V0l7463 (Amir)", + "icon": "KIM/RetroPfpC.png", + "noIconFilter": true + }, + { + "id": "daily_kim_aoi", + "text": "xX GLIMMER Xx (Aoi)", + "icon": "KIM/RetroPfpD.png", + "noIconFilter": true + }, + { + "id": "daily_kim_quincy", + "text": "Soldja1Shot1kil (Quincy)", + "icon": "KIM/RetroPfpF.png", + "noIconFilter": true + } + ] + }, + { + "id": "daily_kim_roundtable_parent", + "text": "The Roundtable", + "prereq": "The Hex (Quest) Finale", + "icon": "KIM/RetroPfpM.png", + "noIconFilter": true, + "subtasks": [ + { + "id": "daily_kim_flare", + "text": "Liminus_Star (Flare)", + "icon": "KIM/RetroPfpJ.png", + "noIconFilter": true, + "prereq": "Rank 4 The Hex" + }, + { + "id": "daily_kim_minerva_velimir", + "text": "MomToxicated & PapaPolar (Minerva & Velimir)", + "icon": "KIM/RetroPfpI.png", + "noIconFilter": true, + "prereq": "Rank 5 The Hex" + }, + { + "id": "daily_kim_kaya", + "text": "KOLTrial_5115 (Kaya)", + "icon": "KIM/RetroPfpH.png", + "noIconFilter": true, + "prereq": "Rank 5 The Hex" + } + ] + }, + { + "id": "daily_kim_devils_triad_parent", + "text": "The Devil's Triad", + "prereq": "The Old Peace", + "icon" : "KIM/Tau_RetroPfpA.png", + "noIconFilter": true, + "subtasks": [ + { + "id": "daily_kim_marie", + "text": "Marie", + "icon": "KIM/Tau_RetroPfpB.png", + "noIconFilter": true + }, + { + "id": "daily_kim_roathe", + "text": "Roathe", + "icon": "KIM/Tau_RetroPfpD.png", + "noIconFilter": true + }, + { + "id": "daily_kim_lyon", + "text": "Lyon", + "icon": "KIM/Tau_RetroPfpC.png", + "noIconFilter": true, + "prereq": "\"Liked\" by Marie" + } + ] + } + ] + }, { "id": "daily_vendors", "text": "Vendors", "icon": "Market.png", - "isParent": true, "subtasks": [ { "id": "daily_acrithis", "text": "Acrithis: Check daily Arcane and Captura offering.", "icon": "Acrithis.png", "location": "Duviri/Dormizone", - "npc": "Acrithis" + "npc": "Acrithis", + "prereq": "Drifter Intrinsics: Opportunity Rank 9 (for Arcanes)" }, { "id": "daily_ticker_crew", @@ -166,7 +281,7 @@ }, { "id": "daily_marie", - "text": "Marie: Purchase Operator and amp mods.", + "text": "Marie: Purchase Tektolyst mods, arcanes, and Perita/Descendia resources.", "icon": "Wisp.png", "location": "La Cathédrale (Sanctum Anatomica, Deimos)", "npc": "Marie", @@ -237,7 +352,6 @@ "id": "weekly_search_pulses", "text": "Search Pulses: Use 5 weekly search pulses on Netracells and Archimedeas.", "icon": "MagnifyingGlassIcon.png", - "isParent": true, "subtasks": [ { "id": "weekly_netracells", @@ -250,7 +364,7 @@ }, { "id": "weekly_eda", - "text": "Elite Deep Archimedea: Attempt weekly Elite Deep Archimedea for high Archon Shard chances (very endgame)", + "text": "Elite Deep Archimedea: Attempt weekly Elite Deep Archimedea for high Archon Shard chances (very endgame).", "icon": "Necraloid.png", "location": "Sanctum Anatomica, Deimos", "npc": "Necraloid", @@ -259,7 +373,7 @@ }, { "id": "weekly_eta", - "text": "Elite Temporal Archimedea: Attempt weekly Elite Temporal Archimedea for high Archon Shard chances (very endgame)", + "text": "Elite Temporal Archimedea: Attempt weekly Elite Temporal Archimedea for high Archon Shard chances (very endgame).", "icon": "Kaya_.png", "location": "Höllvania Central Mall", "npc": "Kaya", @@ -302,7 +416,6 @@ "id": "weekly_vendors", "text": "Vendors", "icon": "Market.png", - "isParent": true, "subtasks": [ { "id": "weekly_iron_wake", @@ -314,15 +427,15 @@ }, { "id": "weekly_yonta", - "text": "Archimedian Yonta: Buy weekly Kuva with Voidplumes.", + "text": "Archimedean Yonta: Buy weekly Kuva with Voidplumes.", "icon": "Yonta.png", "location": "Chrysalith, Zariman", "npc": "Yonta", "prereq": "Angels of the Zariman" }, { - "id": "weekly_acridies", - "text": "Acrithis: Check wares and spend Pathos Clamps if desired (Catalysts/Reactors recommended if needed).", + "id": "weekly_acrithis", + "text": "Acrithis: Check wares and spend Pathos Clamps if desired (Catalysts/Reactors recommended if needed). Buy Kuva with Scuttler Husks.", "icon": "Acrithis.png", "location": "Duviri/Dormizone", "npc": "Acrithis", @@ -351,6 +464,14 @@ "location": "Fortuna, Venus", "npc": "Nightcap", "prereq": "The New War" + }, + { + "id": "weekly_zorba", + "text": "Aspirant Zorba: Trade Atramentum for Kuva.", + "icon": "ZorbaQuickAccessIcon.png", + "location": "Any Relay", + "npc": "Aspirant Zorba", + "prereq": "The Chains of Harrow" } ] } @@ -358,36 +479,63 @@ "other": [ { "id": "other_baro", - "text": "Baro Ki'Teer: Check Baro Ki'Teer's inventory and purchase desired items with Ducats (trade Prime parts for Ducats). (Loading...)", + "text": "Baro Ki'Teer: Check Baro Ki'Teer's inventory and purchase desired items with Ducats (trade Prime parts for Ducats).", "icon": "IconPrimeParts.png", "location": "Relay with Symbol", - "npc": "Baro Ki'Teer" + "npc": "Baro Ki'Teer", + "period": "14d", + "duration": "48h", + "ref": "2022-12-30T09:00:00-05:00", + "observesDst": true }, { "id": "other_grandmother_tokens", - "text": "Mend the Family: Purchase Family Tokens from Grandmother (Loading...)", + "text": "Mend the Family: Purchase Family Tokens from Grandmother.", "icon": "syndicates/IconEntrati.png", - "isEightHourTask": true, "location": "Necralisk, Deimos", - "prereq": "Heart of Deimos" + "prereq": "Heart of Deimos", + "period": "8h", + "ref": "2020-11-19" }, { "id": "other_yonta_voidplumes", - "text": "Trade for Voidplumes (Loading...)", + "text": "Trade for Voidplumes", "icon": "Yonta.png", - "isEightHourTask": true, "location": "Chrysalith, Zariman", "npc": "Yonta", - "prereq": "Angels of the Zariman" + "prereq": "Angels of the Zariman", + "period": "8h", + "ref": "2022-04-27" }, { "id": "other_loid_voca", - "text": "Trade for Voca (Loading...)", + "text": "Trade for Voca", "icon": "MiniMapCaviaVendor_.png", - "isEightHourTask": true, "location": "Sanctum Anatomica, Deimos", "npc": "Loid", - "prereq": "Whispers in the Walls" + "prereq": "Whispers in the Walls", + "period": "8h", + "ref": "2023-12-13" + }, + { + "id": "other_glast", + "text": "Tenet Weapons: Check Ergo Glast's shop for good valence bonuses.", + "icon": "ErgoGlast.png", + "location": "Any Relay", + "npc": "Ergo Glast", + "prereq": "The Archwing + Mastery Rank 14", + "period": "4d", + "ref": "2021-07-08" + }, + { + "id": "other_eleanor", + "text": "Coda Weapons: Check Eleanor's shop for good valence bonuses.", + "icon": "Nyx.png", + "location": "Höllvania Central Mall", + "npc": "Eleanor Nightingale", + "prereq": "The Hex (Quest)", + "period": "4d", + "ref": "2025-03-18" } ] } diff --git a/sources/tests/app.test.js b/sources/tests/app.test.js new file mode 100644 index 0000000..f300dd3 --- /dev/null +++ b/sources/tests/app.test.js @@ -0,0 +1,56 @@ +/// + +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; +import * as app from "../js/app.js"; + +beforeEach(() => { + vi.useFakeTimers(); +}); + +afterEach(() => { + vi.useRealTimers(); +}) + +describe("displayOtherTaskCountdown({period, duration, ref, observesDst})", () => { + beforeEach(() => { + document.getElementsByTagName("body")[0].innerHTML = ` +
+
+ +
+ `; + }); + + afterEach(() => { + document.getElementsByTagName("body")[0].innerHTML = ""; + }); + + test.for([ + ["8h", undefined, "2023-12-13", undefined, "2026-05-18T04:00:00Z", "(Resets in 04:00:00)"], + ["8h", undefined, "2023-12-13", undefined, "2026-05-18T09:30:30Z", "(Resets in 06:29:30)"], + ["8h", undefined, "2023-12-13", undefined, "2026-05-18T16:00:00Z", "(Resets in 08:00:00)"], + ["8h", undefined, "2023-12-13", undefined, "2022-05-18T23:59:59Z", "(Resets in 00:00:01)"], // ref is in the future + ["8h", undefined, "2023-12-13", true, "2022-05-18T23:59:59Z", "(Resets in 07:00:01)"], + ["4d", undefined, "2025-03-18", undefined, "2025-03-18T23:59:59Z", "(Resets in 3d 00:00:01)"], + ["40d", undefined, undefined, undefined, "2026-05-18T16:59:15Z", "(Resets in 8d 07:00:45)"], // undefined ref defaults to zero (1970-01-01) + ["1d", "12h", "2026-05-01", undefined, "2026-05-18T00:30:00Z", "(Available for 11:30:00)"], + ["1d", "12h", "2026-05-01", undefined, "2026-05-18T11:59:00Z", "(Available for 00:01:00)"], + ["1d", "12h", "2026-05-01", undefined, "2026-05-18T12:00:00Z", "(Available in 12:00:00)"], + ["1d", "12h", "2026-05-01", undefined, "2026-05-18T20:45:00Z", "(Available in 03:15:00)"], + ["14d", "2d", "2022-12-30T09:00:00-05:00", true, "2026-01-09T00:00:00-05:00", "(Available in 09:00:00)"], + ["14d", "2d", "2022-12-30T09:00:00-05:00", true, "2026-01-09T09:30:00-05:00", "(Available for 1d 23:30:00)"], + ["14d", "2d", "2022-12-30T09:00:00-05:00", true, "2026-01-11T09:00:00-05:00", "(Available in 12d 00:00:00)"], + ["14d", "2d", "2022-12-30T09:00:00-05:00", true, "2026-03-06T08:00:00-05:00", "(Available in 01:00:00)"], + ["14d", "2d", "2022-12-30T09:00:00-05:00", true, "2026-03-06T09:00:00-05:00", "(Available for 1d 23:00:00)"], // dst means Baro only stays for 47 hours + ["14d", "2d", "2022-12-30T09:00:00-05:00", true, "2026-03-08T09:00:00-05:00", "(Available in 11d 23:00:00)"], // dst changes timezone from -5 to -4, so 9:00(-5) is 10:00(-4) + ["14d", "2d", "2022-12-30T09:00:00-05:00", true, "2026-04-17T09:00:00-04:00", "(Available for 2d 00:00:00)"], + ["14d", "2d", "2022-12-30T09:00:00-05:00", true, "2024-11-01T08:00:00-04:00", "(Available in 01:00:00)"], + ["14d", "2d", "2022-12-30T09:00:00-05:00", true, "2024-11-01T09:00:00-04:00", "(Available for 2d 01:00:00)"], // dst means Baro stays for 49 hours + ["14d", "2d", "2022-12-30T09:00:00-05:00", true, "2024-11-03T09:00:00-04:00", "(Available for 01:00:00)"], // dst changes timezone from -4 to -5, so 9:00(-4) is 8:00(-5) + ["14d", "2d", "2022-12-30T09:00:00-05:00", true, "2024-11-03T09:00:00-05:00", "(Available in 12d 00:00:00)"], + ])("(%s, %s, %s, %s) @ %s -> %s", ([period, duration, ref, observesDst, date, countdown]) => { + vi.setSystemTime(date); + app.displayOtherTaskCountdown({id: "other_test", period, duration, ref, observesDst}); + expect(document.querySelector(".other-countdown").textContent).toEqual(countdown); + }) +}); diff --git a/sources/tests/cycles.schema.json b/sources/tests/cycles.schema.json new file mode 100644 index 0000000..5c12a1f --- /dev/null +++ b/sources/tests/cycles.schema.json @@ -0,0 +1,70 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Warframe Task Checklist cycle data", + + "type": "object", + "$comment": "all properties must be a task `id` in `tasks.json`", + "additionalProperties": false, + "patternProperties": { + "^(daily|weekly|other)_": { + "type": "object", + "required": ["ref", "columns"], + "additionalProperties": false, + "properties": { + "ref": { + "description": "reference time when the first cycle item began", + "type": "string", + "anyOf": [ + { "format": "date-time" }, + { "format": "date" } + ] + }, + "columns": { + "description": "list of columns in the 'Show Schedule' dialog for this cycle. The currently active item from the FIRST column is also shown on the main task list.", + "type": "array", + "items": { + "type": "object", + "required": ["name", "order"], + "additionalProperties": false, + "properties": { + "name": { + "description": "the column header (e.g. Item, Tileset)", + "type": "string" + }, + "align": { + "description": "text-align CSS value to set for this column. When not specified, `order` items without `icon` behave as `center`, and as `left` with `icon`.", + "type": "string", + "enum": ["left", "right", "center", "justify", "start", "end", "match-parent"] + }, + "order": { + "description": "the order in which items are cyclically available. All `order` arrays under the same `columns` must be the same length.", + "$comment": "`order` length equality is checked in `tasks.test.js`", + "type": "array", + "items": { + "type": "object", + "required": ["text"], + "additionalProperties": false, + "properties": { + "text": { + "description": "name of the item", + "type": "string" + }, + "icon": { + "description": "icon for the item, relative to `sources/img/icons/cycles`", + "type": "string" + }, + "iconFilter": { + "description": "set to `true` to convert the `icon` to monochrome black or white depending on the theme. Otherwise, it will be used in color as-is. Note the opposite name and default bahaviour to `tasks.json`", + "type": "boolean", + "default": false + } + } + } + } + } + } + } + } + } + } +} diff --git a/sources/tests/functions.test.js b/sources/tests/functions.test.js new file mode 100644 index 0000000..dbb78d6 --- /dev/null +++ b/sources/tests/functions.test.js @@ -0,0 +1,84 @@ +import { describe, expect, test, vi } from "vitest"; +import { isDst, parseDuration } from "../js/functions.js"; + +describe("Daylight Saving Time", () => { + test.for([ + ["1970-01-01T00:00:00Z", "America/New_York", false], + ["2025-07-01T00:00:00Z", "America/Los_Angeles", true], + ["2025-03-30T00:59:59Z", "Europe/London", false], // just before start + ["2025-03-30T01:00:00Z", "Europe/London", true], // just after start + ["2025-03-09T01:59:59-08:00", "America/Los_Angeles", false], // just before start + ["2025-03-09T02:00:00-08:00", "America/Los_Angeles", true], // just after start + ["2025-03-09T03:00:00-07:00", "America/Los_Angeles", true], // just after start + ["2025-11-02T01:59:59-07:00", "America/Los_Angeles", true], // just before end + ["2025-11-02T02:00:00-07:00", "America/Los_Angeles", false], // just after end + + // non-DST zone + ["2025-01-01T00:00:00Z", "America/Phoenix", false], + ["2025-07-01T00:00:00Z", "America/Phoenix", false], + ["2025-03-09T01:59:59-07:00", "America/Phoenix", false], + ["2025-03-09T02:00:00-07:00", "America/Phoenix", false], + ["2025-03-09T03:00:00-07:00", "America/Phoenix", false], + + // Southern hemisphere + ["2025-01-01T00:00:00Z", "Australia/Sydney", true], + ["2025-07-01T00:00:00Z", "Australia/Sydney", false], + ["2025-04-06T02:59:59+11:00", "Australia/Sydney", true], // just before end + ["2025-04-06T03:00:00+11:00", "Australia/Sydney", false], // just after end + ["2025-04-06T02:00:00+10:00", "Australia/Sydney", false], // just after end + ["2025-10-05T01:59:59+10:00", "Australia/Sydney", false], // just before start + ["2025-10-05T02:00:00+10:00", "Australia/Sydney", true], // just after start + ["2025-10-05T03:00:00+11:00", "Australia/Sydney", true], // just after start + + // fractional time zones + ["2025-05-06T00:00:00Z", "Asia/Kathmandu", false], + ["2025-12-25T00:00:00Z", "Asia/Kathmandu", false], + ["2025-04-06T02:59:59+10:30", "Australia/Adelaide", true], // just before end + ["2025-04-06T03:00:00+10:30", "Australia/Adelaide", false], // just after end + ["2025-04-06T02:00:00+09:30", "Australia/Adelaide", false], // just after end + ["2025-10-05T01:59:59+09:30", "Australia/Adelaide", false], // just before start + ["2025-10-05T02:00:00+09:30", "Australia/Adelaide", true], // just after start + ["2025-10-05T03:00:00+10:30", "Australia/Adelaide", true], // just after start + + // fractional DST + ["2025-04-06T01:59:59+11:00", "Australia/Lord_Howe", true], // just before end + ["2025-04-06T02:00:00+11:00", "Australia/Lord_Howe", false], // just after end + ["2025-04-06T01:30:00+10:30", "Australia/Lord_Howe", false], // just after end + ["2025-10-05T01:59:59+10:30", "Australia/Lord_Howe", false], // just before start + ["2025-10-05T02:00:00+10:30", "Australia/Lord_Howe", true], // just after start + ["2025-10-05T03:00:00+11:00", "Australia/Lord_Howe", true], // just after start + ])(`isDst(%s, %s) -> %o`, ([d, tz, expected]) => { + expect(isDst(new Date(d), tz)).toBe(expected); + expect(isDst(new Date(d).getTime(), tz)).toBe(expected); + expect(isDst(d, tz)).toBe(expected); + }); +}); + + +describe("parseDuration", () => { + test.for([ + ["1s", 1000], + ["15s", 15_000], + ["3m", 180_000], + ["12h", 43_200_000], + ["14d", 1_209_600_000], + ["2d3H10M12s", 184_212_000], + ["1m, 359h 1d 3d, 5m6s 4h", 1_652_766_000] + ])(`parseDuration(%s) -> %i`, ([d, expected]) => { + expect(parseDuration(d)).toEqual(expected); + }); + + test.for(["", 0, 1000])("%o", (i) => { + expect(parseDuration(i)).toBeUndefined(); + }); + + test.for([ + ["1s 10n", 1, 1000], + ["10y5w2d3h8x10m12s13g", 4, 184_212_000], + ["1m, 359h 9p1d 3d, 5m6s 4h\t1234v", 2, 1_652_766_000] + ])(`parseDuration(%s) warn %i`, ([d, warnings, expected]) => { + const consoleWarn = vi.spyOn(console, "warn"); + expect(parseDuration(d)).toEqual(expected); + expect(consoleWarn).toHaveBeenCalledTimes(warnings); + }); +}); diff --git a/sources/tests/tasks.schema.json b/sources/tests/tasks.schema.json new file mode 100644 index 0000000..9bcedf7 --- /dev/null +++ b/sources/tests/tasks.schema.json @@ -0,0 +1,144 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Warframe Task Checklist tasks", + + "type": "object", + "properties": { + "daily": { + "type": "array", + "items": { + "$ref": "#/$defs/task", "$comment": "base task schema", + "type": "object", + "properties": { + "id": { + "$comment": "augment task definition with an id pattern for this section", + "type": "string", + "pattern": "^daily_" + }, + "subtasks": { "$ref": "#/properties/daily", "$comment": "propagate augmented task definition to nested subtasks" } + }, + "not": { "required": ["period"] } + } + }, + "weekly": { + "type": "array", + "items": { + "$ref": "#/$defs/task", + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^weekly_" + }, + "subtasks": { "$ref": "#/properties/weekly" } + }, + "not": { "required": ["period"] } + } + }, + "other": { + "type": "array", + "items": { + "$ref": "#/$defs/task", + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^other_" + }, + "subtasks": { "$ref": "#/properties/other" } + }, + "required": ["period", "ref"] + } + } + }, + "required": ["daily", "weekly", "other"], + "additionalProperties": false, + + "$defs": { + "task": { + "type": "object", + "properties": { + "id": { + "description": "unique identifier for the task", + "type": "string" + }, + "text": { + "description": "displayed text of the task", + "type": "string" + }, + "icon": { + "description": "icon for the task, relative to `sources/img/icons/tasks`", + "type": "string" + }, + "noIconFilter": { + "description": "set to `true` to use the `icon` in color as-is. Otherwise, it will be converted to monochrome black or white depending on the theme", + "type": "boolean", + "default": false + }, + "location": { + "description": "in-game location where you initiate the task", + "type": "string" + }, + "npc": { + "description": "NPC to talk to to initiate the task", + "type": "string" + }, + "terminal": { + "description": "interactive terminal used to initiate the task", + "type": "string" + }, + "prereq": { + "description": "task prerequisites (e.g. completed quest, syndicate rank)", + "type": "string" + }, + "info": { + "description": "a short bit of additional info about the task, displayed below the `text` on the main task list. For extended information in a dialog, use `moreInfo.js`", + "type": "string" + }, + "period": { + "description": "how often the task resets", + "$ref": "#/$defs/timePeriod" + }, + "duration": { + "description": "how long the task is available for after resetting until it becomes unavailable", + "$ref": "#/$defs/timePeriod" + }, + "ref": { + "description": "reference time when the task reset. Resets are calculated to occur at `ref + n * period` where `n` is an integer", + "type": "string", + "anyOf": [ + { "format": "date-time" }, + { "format": "date" } + ] + }, + "observesDst": { + "description": "whether the task's reset time shifts by 1 hour when Daylight Saving Time changes", + "type": "boolean", + "default": false + }, + "subtasks": { + "description": "tasks grouped under this parent task", + "type": "array", + "items": { "$ref": "#/$defs/task" } + } + }, + "additionalProperties": false, + "required": ["id", "text"], + "dependentSchemas": { + "npc": { + "not": { + "required": ["terminal"], + "description": "if `npc` is present, then `terminal` is forbidden" + } + } + } + }, + + "timePeriod": { + "description": "a period of time / a duration", + "type": "string", + "pattern": "^(0*[1-9]\\d*[DdHhMmSs] ?)+$", + "$comment": "this is more strict than `parseDuration` actually allows" + } + } +} diff --git a/sources/tests/tasks.test.js b/sources/tests/tasks.test.js new file mode 100644 index 0000000..9988ade --- /dev/null +++ b/sources/tests/tasks.test.js @@ -0,0 +1,78 @@ +import { describe, expect, test } from "vitest"; +import Ajv from "ajv/dist/2020"; +import addFormats from "ajv-formats"; + +import tasks from "../js/tasks.json" with {type: "json"}; +import tasks_schema from "./tasks.schema.json" with {type: "json"}; +import cycles from "../js/cycles.json" with {type: "json"}; +import cycles_schema from "./cycles.schema.json" with {type: "json"}; +import moreInfo from "../js/moreInfo"; + +const ajv = new Ajv({ allErrors: true }); +addFormats(ajv); + +const validate_tasks_schema = ajv.compile(tasks_schema); +const validate_cycles_schema = ajv.compile(cycles_schema); + +describe("valildate task definitions", () => { + test.for([ + ["tasks", validate_tasks_schema, tasks], + ["cycles", validate_cycles_schema, cycles] + ])("validate %s against schema", ([which, validation_function, data]) => { + const valid = validation_function(data); + if (!valid) { console.error(validation_function.errors); } + const message = `${which} schema validation failed:\n${JSON.stringify(validation_function.errors, null, 4)}\n`; + expect(valid, message).toBeTruthy(); + }); + + // flatten the nested tree of [sub]tasks into an array + let stack = []; + let flatTasks = []; + for (const section in tasks) { + for (const t of tasks[section]) { + stack.push(t); + } + } + while (stack.length) { + const t = stack.pop(); + flatTasks.push(t); + if (t.subtasks) { + for (const s of t.subtasks) { + stack.push(s); + } + } + } + + const task_ids = flatTasks.map((t) => t.id); + + test("verify unique task ids", () => { + let used_ids = []; + for (const id of task_ids) { + expect(used_ids, "task ids must be unique").not.toContain(id); + used_ids.push(id); + } + }); + + const cycle_keys_for_test = Object.keys(cycles).map((i) => [i]); + + describe("verify equal `order` lengths", () => { + test.for(cycle_keys_for_test)("%s", ([task_id]) => { + for (const col of cycles[task_id].columns) { + expect(col.order.length, "column lengths are not equal").toEqual(cycles[task_id].columns[0].order.length) + } + }); + }); + + describe("verify cycle task ids", () => { + test.for(cycle_keys_for_test)("%s", ([task_id]) => { + expect(task_ids, "cycle keys must be task ids from `tasks.json`").toContain(task_id); + }); + }); + + describe("validate moreInfo", () => { + test.for(Object.keys(moreInfo).map((i) => [i]))("%s", ([task_id]) => { + expect(task_ids, "moreInfo keys must be task ids from `tasks.json`").toContain(task_id); + expect(moreInfo[task_id]).toBeTypeOf("string"); + }); + }); +}); diff --git a/vite.config.js b/vite.config.js index b985ebe..c4d7e3d 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,9 +1,6 @@ // vite.config.js import { defineConfig } from "vite"; -import { createHtmlPlugin } from "vite-plugin-html"; import { viteSingleFile } from "vite-plugin-singlefile"; -import fs from "node:fs"; -import path from "node:path"; import { execSync } from "node:child_process"; process.env.VITE_GIT_COMMIT_HASH = execSync("git rev-parse HEAD").toString().trim(); // only updated on vite restart @@ -11,16 +8,9 @@ process.env.VITE_GIT_COMMIT_HASH = execSync("git rev-parse HEAD").toString().tri export default defineConfig(({ mode }) => ({ root: "sources", base: "/Task-Checklist/", + cacheDir: "../.vite", plugins: [ - createHtmlPlugin({ - minify: true, - inject: { - data: { - criticalCss: ``, - }, - }, - }), mode === "release" ? viteSingleFile({removeViteModuleLoader: true}) : null, ], @@ -28,4 +18,13 @@ export default defineConfig(({ mode }) => ({ outDir: mode === "release" ? "../release" : "../pages", emptyOutDir: true, }, + + test: { + environment: "jsdom", + restoreMocks: true, + coverage: { + reportsDirectory: "../.coverage", + exclude: ["img/", "*.json"] + } + } }));