commit fe11e264de0b296f5d7675396469d82bf4b98d6b Author: Eugene Fox Date: Tue Nov 18 20:16:48 2025 +0000 init: initial commit diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..e9b2061 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,56 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet +{ + "name": "bonch-calendar", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/dotnet:1-10.0", + // Features to add to the dev container. More info: https://containers.dev/features. + "features": { + "ghcr.io/devcontainers/features/node:1": { + "version": "lts", + "pnpmVersion": "none", + "nvmVersion": "latest" + }, + "ghcr.io/devcontainers/features/docker-in-docker:2": { + "installDockerBuildx": true, + "version": "latest", + "dockerDashComposeVersion": "v2" + } + }, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [8000, 8080], + // "portsAttributes": { + // "5001": { + // "protocol": "https" + // } + // } + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": { + "api": "dotnet restore api/BonchCalendar.csproj", + "app": "cd app && npm install" + }, + + // Configure tool-specific properties. + "customizations": { + "vscode": { + "extensions": [ + "bierner.github-markdown-preview", + "dbaeumer.vscode-eslint", + "github.vscode-github-actions", + "GitHub.vscode-pull-request-github", + "Gruntfuggly.todo-tree", + "jock.svg", + "ms-azuretools.vscode-docker", + "ms-dotnettools.csdevkit", + "patcx.vscode-nuget-gallery", + "saeris.markdown-github-alerts", + "humao.rest-client" + ] + } + } + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..b5b17b5 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @XFox111 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..d4311d7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,102 @@ +name: "🐞 Bug Report" +description: Create a report to help us improve the project +title: "[Bug]: " +labels: ["bug", "needs-triage"] +assignees: + - xfox111 +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + + - type: textarea + id: desc + attributes: + label: Description + description: A clear and concise description of what the bug is. + placeholder: e.g. The calendar is empty, even though timetable for my group is published. + validations: + required: true + + - type: textarea + attributes: + label: Reproduction steps + description: Precisely describe minimal number of steps that make the bug to appear + placeholder: | + 1. Go to '...' + 2. Click on '...' + 3. Scroll down to '...' + 4. See '...' + validations: + required: true + + - type: textarea + attributes: + label: Expected behavior + description: A clear and concise description of what you expected to happen. + placeholder: e.g. My timetable should be displayed in the calendar. + validations: + required: true + + - type: textarea + attributes: + label: Screenshot + description: If applicable, add screenshots to help explain your problem. + validations: + required: false + + - type: input + id: browser + attributes: + label: Browser name + placeholder: e.g. Google Chrome + validations: + required: true + + - type: input + id: calendar + attributes: + label: Calendar service + placeholder: e.g. Google Calendar, Outlook, etc. + validations: + required: true + + - type: input + id: group + attributes: + label: Faculty, course number and group number + description: | + If you don't want to share your group publicly, say "PM", and send it to feedback@xfox111.net. Reference the issue number. + placeholder: e.g. РТС, 2, РТ-31м + validations: + required: true + + - type: textarea + id: context + attributes: + label: Additional context + description: Add any other context about the problem here. + validations: + required: false + + - type: dropdown + id: requested-help + attributes: + label: Are you willing to submit a PR for this issue? + options: + - "yes" + - "no" + validations: + required: true + + - type: checkboxes + id: checkboxes + attributes: + label: Validations + description: Before submitting the issue, please make sure you do the following + options: + - label: Check that there isn't already an issue that reports the same bug to avoid creating a duplicate. + required: true + - label: The provided reproduction is a minimal reproducible example of the bug. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..c132cea --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-issue-config.json + +blank_issues_enabled: true +contact_links: + - name: Questions & Discussions + url: https://github.com/XFox111/bonch-calendar/discussions + about: Use GitHub discussions to ask your questions. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..af4b8cb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,62 @@ +name: "🚀 New feature proposal" +description: Suggest a feature idea for this project +title: "[Feature]: " +labels: ["feature", "needs-triage"] +assignees: + - xfox111 +body: + - type: markdown + attributes: + value: | + Thanks for your interest in the project and taking the time to fill out this feature report! + + - type: textarea + id: proposition + attributes: + label: Proposed solution + description: Describe the solution you'd like + validations: + required: true + + - type: textarea + id: justification + attributes: + label: Justification + description: Is your feature request related to a problem? Please describe. + validations: + required: true + + - type: textarea + id: alts + attributes: + label: Alternatives + description: Describe alternatives you've considered. + validations: + required: true + + - type: textarea + id: context + attributes: + label: Additional context + description: Add any other context or screenshots about the feature request here. + validations: + required: false + + - type: dropdown + id: requested-help + attributes: + label: Are you willing to submit a PR for this issue? + options: + - "yes" + - "no (I am aware that the project's author will not work on this issue)" + validations: + required: true + + - type: checkboxes + id: checkboxes + attributes: + label: Validations + description: Before submitting the issue, please make sure you do the following + options: + - label: Check that there isn't already an issue that request the same feature to avoid creating a duplicate. + required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a7784a8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,81 @@ +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +# yaml-language-server: $schema=https://json.schemastore.org/dependabot-2.0.json + +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/app" + target-branch: "main" + assignees: + - "XFox111" + schedule: + interval: monthly + rebase-strategy: disabled + groups: + deps: + patterns: + - "*" + exclude-patterns: + - "react" + - "react-dom" + - "@types/react" + - "@types/react-dom" + react: + patterns: + - "react" + - "react-dom" + - "@types/react" + - "@types/react-dom" + update-types: + - minor + - patch + react-next: + patterns: + - "react" + - "react-dom" + - "@types/react" + - "@types/react-dom" + update-types: + - major + + - package-ecosystem: "nuget" # See documentation for possible values + directory: "/" # Location of package manifests + target-branch: "main" + assignees: + - "XFox111" + schedule: + interval: monthly + rebase-strategy: disabled + open-pull-requests-limit: 20 + + - package-ecosystem: "github-actions" + directory: "/" + target-branch: "main" + assignees: + - "XFox111" + schedule: + interval: monthly + rebase-strategy: disabled + open-pull-requests-limit: 20 + + - package-ecosystem: "devcontainers" + directory: "/" + target-branch: "main" + assignees: + - "XFox111" + schedule: + interval: monthly + rebase-strategy: disabled + open-pull-requests-limit: 20 + + - package-ecosystem: "docker" + directory: "/" + target-branch: "main" + assignees: + - "XFox111" + schedule: + interval: monthly + rebase-strategy: disabled + open-pull-requests-limit: 20 diff --git a/.github/pull_request_tempate.md b/.github/pull_request_tempate.md new file mode 100644 index 0000000..e603924 --- /dev/null +++ b/.github/pull_request_tempate.md @@ -0,0 +1,6 @@ +## Description + + + + + diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 0000000..f11146d --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,88 @@ +name: "Audit pipeline" + +on: + push: + branches: [ "main" ] + paths-ignore: + - '.devcontainer/*' + - '.github/*' + - '!.github/workflows/audit.yml' + - '.vscode/*' + - '**.md' + - 'LICENSE' + - 'assets/*' + pull_request: + branches: [ "main" ] + paths-ignore: + - '.devcontainer/*' + - '.github/*' + - '!.github/workflows/audit.yml' + - '.vscode/*' + - '**.md' + - 'LICENSE' + - 'assets/*' + workflow_dispatch: + +permissions: + packages: write + +jobs: + api: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + + - uses: docker/build-push-action@v6 + with: + context: ./api + tags: ${{ github.repository }}-api:ci + + - run: docker save ${{ github.repository }}:ci | gzip > api_image.tar.gz + + - uses: actions/upload-artifact@v5 + with: + name: api-image + path: api_image.tar.gz + + app: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + + - uses: docker/build-push-action@v6 + with: + context: ./app + tags: ${{ github.repository }}-app:ci + + - run: docker save ${{ github.repository }}:ci | gzip > app_image.tar.gz + + - uses: actions/upload-artifact@v5 + with: + name: app-image + path: app_image.tar.gz + + app_audit: + runs-on: ubuntu-latest + container: node:latest + + steps: + - uses: actions/checkout@v5 + + - run: npm install + working-directory: ./app + + - run: npm run lint + working-directory: ./app + + - run: npm audit --audit-level=moderate + working-directory: ./app + + - run: npm audit --audit-level=moderate --json > audit_report.json + working-directory: ./app + + - uses: actions/upload-artifact@v5 + with: + name: app-audit-report + path: ./app/audit_report.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a81919e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,110 @@ +name: "Release pipeline" + +on: + release: + types: [published] + workflow_dispatch: + +permissions: + packages: write + +jobs: + api: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + + - uses: docker/metadata-action@v5 + id: meta + with: + images: | + ${{ github.repository }}-api + ghcr.io/${{ github.repository }}-api + tags: | + latest + ${{ github.ref_name }} + + - name: "Login to Docker Hub" + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: "Login to GitHub Container Registry" + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: ./api + push: true + tags: ${{ steps.meta.outputs.tags }} + + app: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + + - uses: docker/metadata-action@v5 + id: meta + with: + images: | + ${{ github.repository }}-app + ghcr.io/${{ github.repository }}-app + tags: | + latest + ${{ github.ref_name }} + + - name: "Login to Docker Hub" + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: "Login to GitHub Container Registry" + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: ./app + push: true + tags: ${{ steps.meta.outputs.tags }} + + pages: + runs-on: ubuntu-latest + container: node:latest + + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - uses: actions/checkout@v5 + + - run: npm install + working-directory: ./app + + - run: npm run build + env: + VITE_BACKEND_HOST: https://api.bonch.xfox111.net + working-directory: ./app + + - name: Setup Pages + uses: actions/configure-pages@v5 + + - uses: actions/upload-pages-artifact@v4 + with: + path: "./app/dist" + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..fb01407 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,15 @@ +{ + "recommendations": [ + "bierner.github-markdown-preview", + "github.vscode-github-actions", + "GitHub.vscode-pull-request-github", + "Gruntfuggly.todo-tree", + "ms-dotnettools.csdevkit", + "patcx.vscode-nuget-gallery", + "saeris.markdown-github-alerts", + "dbaeumer.vscode-eslint", + "jock.svg", + "ms-azuretools.vscode-docker", + "humao.rest-client" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..fbebf23 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,25 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + }, + { + "name": "API: Launch BonchCalendar", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "api: build", + "program": "${workspaceFolder}/api/bin/Debug/net10.0/BonchCalendar.dll", + "cwd": "${workspaceFolder}/api", + "stopAtEntry": false, + "console": "internalConsole", + "suppressJITOptimizations": true, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "http://localhost:8080" + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..212a1c3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,19 @@ +{ + "editor.rulers": [ + { + "column": 120 + } + ], + "editor.insertSpaces": false, + "files.insertFinalNewline": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "always" + }, + "files.eol": "\n", + "files.trimFinalNewlines": true, + "files.trimTrailingWhitespace": true, + "todo-tree.filtering.excludeGlobs": [ + "**/node_modules/*/**", + "README.md" + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..6abb3c6 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,148 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "api: build", + "command": "dotnet", + "type": "process", + "group": "build", + "options": { + "cwd": "${workspaceFolder}/api" + }, + "args": [ + "build", + "BonchCalendar.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "api: publish", + "command": "dotnet", + "type": "process", + "group": "build", + "options": { + "cwd": "${workspaceFolder}/api" + }, + "args": [ + "publish", + "BonchCalendar.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "api: watch", + "command": "dotnet", + "type": "process", + "group": "test", + "options": { + "cwd": "${workspaceFolder}/api" + }, + "args": [ + "watch", + "run", + "--project", + "BonchCalendar.sln" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "app: install", + "type": "npm", + "script": "install", + "group": "build", + "options": { + "cwd": "${workspaceFolder}/app" + }, + "problemMatcher": [] + }, + { + "label": "app: build", + "type": "npm", + "script": "build", + "group": "build", + "options": { + "cwd": "${workspaceFolder}/app" + }, + "problemMatcher": [] + }, + { + "label": "app: dev", + "type": "npm", + "script": "dev", + "group": "test", + "options": { + "cwd": "${workspaceFolder}/app" + }, + "problemMatcher": [] + }, + { + "label": "app: lint", + "type": "npm", + "script": "lint", + "options": { + "cwd": "${workspaceFolder}/app" + }, + "problemMatcher": [] + }, + { + "label": "app: preview", + "type": "npm", + "script": "preview", + "group": "test", + "options": { + "cwd": "${workspaceFolder}/app" + }, + "problemMatcher": [] + }, + { + "label": "docker: build (app)", + "type": "process", + "command": "docker", + "group": "build", + "options": { + "cwd": "${workspaceFolder}/app" + }, + "args": [ + "build", + "-t xfox111/bonch-calendar-app:latest", + "." + ], + "problemMatcher": [] + }, + { + "label": "docker: build (api)", + "type": "process", + "command": "docker", + "group": "build", + "options": { + "cwd": "${workspaceFolder}/api" + }, + "args": [ + "build", + "-t xfox111/bonch-calendar-api:latest", + "." + ], + "problemMatcher": [] + }, + { + "label": "docker: compose up", + "type": "process", + "command": "docker", + "group": "test", + "options": { + "cwd": "${workspaceFolder}" + }, + "args": [ + "compose", + "up", + "--build", + "--force-recreate" + ], + "problemMatcher": [] + }, + ] +} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..18231ab --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,135 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[opensource@xfox111.net](mailto:opensource@xfox111.net). +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +> Contributor Covenant is released under the [Creative Commons Attribution 4.0 International Public License](https://github.com/EthicalSource/contributor_covenant/blob/release/LICENSE.md). + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..220f858 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,26 @@ +# Contributing Guidelines + +This document will guide you through the process of contributing to this project. We welcome contributions from the community and appreciate your efforts to improve it. + +> [!NOTE] +> This document is a work in progress. More information will be added later. + +## Asking questions + +## Submitting bug reports and feature requests + +## Code contributions + +### What should I know/learn before I start? + +### Setting up development environment + +#### With devcontainers + +#### Without devcontainers + +### Building the project + +### Getting familiar with the codebase + +### Submitting a pull request diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..db0177c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Eugene Fox + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0fdbf1d --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +[![Website status](https://img.shields.io/website?url=http%3A//bonch.xfox111.net/)](https://bonch.xfox111.net) +[![GitHub release (latest by date)](https://img.shields.io/github/v/release/xfox111/bonch-calendar)](https://github.com/xfox111/bonch-calendar/releases/latest) +[![GitHub last commit](https://img.shields.io/github/last-commit/xfox111/bonch-calendar?label=Last+update)](https://github.com/XFox111/bonch-calendar/commits/main) + + + + + Bonch.Calendar. Check your SPbSUT timetable in your calendar! + + +A simple service that provides web calendars that you can subscribe to in your calendar application (Google Calendar, Apple Calendar, Outlook, etc.) and see your group's timetable there. + +This is my farewell gift to the university. + +## Demo + + + +## Q&A + +### Q: I have questions. Where do I ask? + +You can go to the [discussions tab](https://github.com/bonch-calendar/discussions) to ask questions or start a discussion. + +But before that, check out the [FAQ section](https://bonch.xfox111.net/#faq) on the website, as your question may already be answered there. + +### Q: I want to contribute to the project. How can I do that? + +First, you can check out [open issues](https://github.com/bonch-calendar/issues) or [discussions](https://github.com/bonch-calendar/discussions) to see if there are any tasks you can help with. + +If you already found one, or have an idea for a new feature or improvement, you can create a pull request with your changes. + +The [contributing guidelines](CONTRIBUTING.md) contain all the information you need. + +> [!NOTE] +> Before starting to work on a new feature or bugfix, it's a good idea to open an issue first to discuss it with the maintainers. This way, you can ensure that your efforts align with the project's needs and avoid duplicating work. + +### Q: I'd like to become a maintainer. How can I do that? + +If you're interested in becoming a maintainer, please reach out to me via email at [eugene@xfox111.net](mailto:eugene@xfox111.net). You'll have to show that you're capable though. + +--- + +[![Bluesky](https://img.shields.io/badge/%40xfox111.net-BSky?logo=bluesky&logoColor=%230285FF&label=Bluesky&labelColor=white&color=%230285FF)](https://bsky.app/profile/xfox111.net) +[![GitHub](https://img.shields.io/badge/%40xfox111-GitHub?logo=github&logoColor=%23181717&label=GitHub&labelColor=white&color=%23181717)](https://github.com/xfox111) +[![Buy Me a Coffee](https://img.shields.io/badge/%40xfox111-BMC?logo=buymeacoffee&logoColor=black&label=Buy%20me%20a%20coffee&labelColor=white&color=%23FFDD00)](https://buymeacoffee.com/xfox111) + +> ©2025 Eugene Fox. Licensed under [MIT license](https://github.com/XFox111/bonch-calendar/blob/main/LICENSE) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..47bed35 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,8 @@ +# Security Policy + +We as maintainers of this project are committed to maintaining the security of our software. We take security vulnerabilities seriously and will work to address them as quickly as possible. + +We regularly run security audits and fix any security issues that are found. If you find a security issue, please report it to us as described below. + +## Reporting a Vulnerability +You can report a security issue by going through [this link](https://github.com/XFox111/bonch-calendar/security/advisories/new) diff --git a/api/.dockerignore b/api/.dockerignore new file mode 100644 index 0000000..0808c4a --- /dev/null +++ b/api/.dockerignore @@ -0,0 +1,482 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from `dotnet new gitignore` + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +# but not Directory.Build.rsp, as it configures directory-level build defaults +!Directory.Build.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea/ + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp diff --git a/api/.gitignore b/api/.gitignore new file mode 100644 index 0000000..0808c4a --- /dev/null +++ b/api/.gitignore @@ -0,0 +1,482 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from `dotnet new gitignore` + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +# but not Directory.Build.rsp, as it configures directory-level build defaults +!Directory.Build.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea/ + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp diff --git a/api/BonchCalendar.csproj b/api/BonchCalendar.csproj new file mode 100644 index 0000000..6c091eb --- /dev/null +++ b/api/BonchCalendar.csproj @@ -0,0 +1,16 @@ + + + + net10.0 + enable + enable + + + + + + + + + + diff --git a/api/BonchCalendar.http b/api/BonchCalendar.http new file mode 100644 index 0000000..104c041 --- /dev/null +++ b/api/BonchCalendar.http @@ -0,0 +1,23 @@ +@Host = http://localhost:8080 + +GET {{Host}}/health +Accept: application/json + +### + +GET {{Host}}/faculties +Accept: application/json + +### + +GET {{Host}}/groups + ?facultyId=56682 + &course=2 +Accept: application/json + +### + +@groupId = 56606 +@facultyId = 50029 +GET {{Host}}/timetable/{{facultyId}}/{{groupId}} +Accept: text/calendar diff --git a/api/BonchCalendar.sln b/api/BonchCalendar.sln new file mode 100644 index 0000000..8b9bbd0 --- /dev/null +++ b/api/BonchCalendar.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BonchCalendar", "BonchCalendar.csproj", "{811C13A0-E5FC-452C-8628-AD36B9A8A7E2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {811C13A0-E5FC-452C-8628-AD36B9A8A7E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {811C13A0-E5FC-452C-8628-AD36B9A8A7E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {811C13A0-E5FC-452C-8628-AD36B9A8A7E2}.Debug|x64.ActiveCfg = Debug|Any CPU + {811C13A0-E5FC-452C-8628-AD36B9A8A7E2}.Debug|x64.Build.0 = Debug|Any CPU + {811C13A0-E5FC-452C-8628-AD36B9A8A7E2}.Debug|x86.ActiveCfg = Debug|Any CPU + {811C13A0-E5FC-452C-8628-AD36B9A8A7E2}.Debug|x86.Build.0 = Debug|Any CPU + {811C13A0-E5FC-452C-8628-AD36B9A8A7E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {811C13A0-E5FC-452C-8628-AD36B9A8A7E2}.Release|Any CPU.Build.0 = Release|Any CPU + {811C13A0-E5FC-452C-8628-AD36B9A8A7E2}.Release|x64.ActiveCfg = Release|Any CPU + {811C13A0-E5FC-452C-8628-AD36B9A8A7E2}.Release|x64.Build.0 = Release|Any CPU + {811C13A0-E5FC-452C-8628-AD36B9A8A7E2}.Release|x86.ActiveCfg = Release|Any CPU + {811C13A0-E5FC-452C-8628-AD36B9A8A7E2}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/api/Dockerfile b/api/Dockerfile new file mode 100644 index 0000000..36afc26 --- /dev/null +++ b/api/Dockerfile @@ -0,0 +1,15 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build +WORKDIR /build + +ADD *.csproj . +RUN dotnet restore + +ADD . ./ +RUN dotnet publish --no-restore --configuration Release --output /out + +FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS prod +WORKDIR /app + +COPY --from=build /out/* . + +ENTRYPOINT [ "dotnet", "BonchCalendar.dll" ] diff --git a/api/Health/ApiHealthCheck.cs b/api/Health/ApiHealthCheck.cs new file mode 100644 index 0000000..d3e9fe0 --- /dev/null +++ b/api/Health/ApiHealthCheck.cs @@ -0,0 +1,26 @@ +using BonchCalendar.Services; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace BonchCalendar.Health; + +public class ApiHealthCheck(ApiService groupService) : IHealthCheck +{ + public async Task CheckHealthAsync( + HealthCheckContext context, CancellationToken cancellationToken = default + ) + { + try + { + Dictionary faculties = await groupService.GetFacultiesListAsync(); + + if (faculties.Count > 0) + return HealthCheckResult.Healthy(); + + return HealthCheckResult.Degraded(description: "Timetable website looks to be up, but returned an empty list of faculties."); + } + catch (Exception ex) + { + return HealthCheckResult.Unhealthy(description: "Timetable website appears to be down.", exception: ex); + } + } +} diff --git a/api/Program.cs b/api/Program.cs new file mode 100644 index 0000000..687d59c --- /dev/null +++ b/api/Program.cs @@ -0,0 +1,130 @@ + +using System.ComponentModel.DataAnnotations; +using BonchCalendar; +using BonchCalendar.Health; +using BonchCalendar.Services; +using BonchCalendar.Utils; +using HealthChecks.UI.Client; +using Ical.Net; +using Ical.Net.CalendarComponents; +using Ical.Net.Serialization; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.AspNetCore.Mvc; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); +builder.Services.AddValidation(); +builder.Services.AddProblemDetails(configure => +{ + configure.CustomizeProblemDetails = context => + { + context.ProblemDetails.Extensions["traceId"] = context.HttpContext.TraceIdentifier; + context.ProblemDetails.Extensions["naas_reason"] = new NaasReasons().GetReason(); + }; +}); + +builder.Services + .AddScoped() + .AddScoped(); + +builder.Services.AddHealthChecks() + .AddCheck("timetable_website"); + +builder.Services.AddCors(options => + options.AddDefaultPolicy(policy => + policy + .WithMethods(["GET"]) + .AllowAnyOrigin() + .AllowAnyHeader() + ) +); + +WebApplication app = builder.Build(); + +// Configure the HTTP request pipeline. +app.UseCors(); +app.UseStatusCodePages(); +app.MapOpenApi(); + +app.MapHealthChecks("/health", new HealthCheckOptions +{ + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse +}); + +ILogger logger = app.Services.GetRequiredService>(); + +app.MapGet("/faculties", async ([FromServices] ApiService apiService) => +{ + logger.LogInformation("Fetching faculties list."); + Dictionary faculties = await apiService.GetFacultiesListAsync(); + return Results.Ok(faculties); +}) + .WithName("GetFaculties") + .WithDescription("Gets the list of faculties.") + .Produces>(StatusCodes.Status200OK); + +app.MapGet("/groups", async ([FromServices] ApiService apiService, int facultyId, [Range(1, 5)] int course) => +{ + logger.LogInformation("Fetching groups list for faculty {FacultyId} and course {Course}.", facultyId, course); + Dictionary groups = await apiService.GetGroupsListAsync(facultyId, course); + return Results.Ok(groups); +}) + .WithName("GetGroups") + .WithDescription("Gets the list of groups for the specified faculty and course.") + .Produces>(StatusCodes.Status200OK) + .ProducesValidationProblem(); + +app.MapGet("/timetable/{facultyId}/{groupId}", async ( + int facultyId, int groupId, + [FromServices] ApiService apiService, + [FromServices] ParsingService parsingService +) => +{ + logger.LogInformation("Generating timetable for group {GroupId} of faculty {FacultyId}.", groupId, facultyId); + string cacheFile = Path.Combine(Path.GetTempPath(), $"bonch_cal_{groupId}.ics"); + + if (File.Exists(cacheFile) && (DateTime.UtcNow - File.GetLastWriteTimeUtc(cacheFile)).TotalHours < 6) + { + if (args.Contains("--no-cache")) + logger.LogWarning("Cache disabled via --no-cache, regenerating timetable for group {GroupId}.", groupId); + else + { + logger.LogInformation("Serving timetable for group {GroupId} from cache.", groupId); + return Results.Text(await File.ReadAllTextAsync(cacheFile), contentType: "text/calendar"); + } + } + + DateTime semesterStartDate = await apiService.GetSemesterStartDateAsync(groupId); + string groupName = (await apiService.GetGroupsListAsync(facultyId, 0))[groupId]; + + string classesRaw = await apiService.GetScheduleDocumentAsync(groupId, TimetableType.Classes); + List timetable = [.. parsingService.ParseGeneralTimetable(classesRaw, semesterStartDate, groupName)]; + + TimetableType[] types = [TimetableType.Attestations, TimetableType.Exams, TimetableType.ExamsForExtramural]; + foreach (TimetableType type in types) + { + classesRaw = await apiService.GetScheduleDocumentAsync(groupId, type); + timetable.AddRange(parsingService.ParseExamTimetable(classesRaw, groupName)); + } + + Calendar calendar = new(); + calendar.Properties.Add(new CalendarProperty("X-WR-CALNAME", groupName)); + calendar.Properties.Add(new CalendarProperty("X-WR-TIMEZONE", "Europe/Moscow")); + calendar.Properties.Add(new CalendarProperty("REFRESH-INTERVAL;VALUE=DURATION", "PT6H")); + calendar.Events.AddRange(timetable); + calendar.AddTimeZone(new VTimeZone("Europe/Moscow")); + string serialized = new CalendarSerializer().SerializeToString(calendar)!; + + await File.WriteAllTextAsync(cacheFile, serialized); + logger.LogInformation("Cached timetable for group {GroupId} to {CacheFile}.", groupId, cacheFile); + return Results.Text(serialized, contentType: "text/calendar"); +}) + .WithName("GetTimetable") + .WithDescription("Gets the iCal timetable for the specified group.") + .Produces(StatusCodes.Status200OK, "text/calendar") + .ProducesValidationProblem(); + +app.Run(); diff --git a/api/Properties/launchSettings.json b/api/Properties/launchSettings.json new file mode 100644 index 0000000..8ef88b9 --- /dev/null +++ b/api/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:8080", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:8443;http://localhost:8080", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/api/Services/ApiService.cs b/api/Services/ApiService.cs new file mode 100644 index 0000000..e402983 --- /dev/null +++ b/api/Services/ApiService.cs @@ -0,0 +1,91 @@ +using AngleSharp.Html.Dom; +using AngleSharp.Html.Parser; +using BonchCalendar.Utils; + +namespace BonchCalendar.Services; + +public class ApiService +{ + public async Task> GetFacultiesListAsync() => + ParseListResponse(await SendRequestAsync(new() + { + ["choice"] = "1", // "choice" is always "1" (idk why, don't ask me) + ["schet"] = GetCurrentSemesterId() + })); + + public async Task> GetGroupsListAsync(int facultyId, int course) => + ParseListResponse(await SendRequestAsync(new() + { + ["choice"] = "1", + ["schet"] = GetCurrentSemesterId(), + ["faculty"] = facultyId.ToString(), // Specifying faculty ID returns a list of groups + ["kurs"] = course.ToString() // Course number is actually optional, but filters out other groups. Can be set 0 to get all groups of the faculty. + })); + + public async Task GetScheduleDocumentAsync(int groupId, TimetableType timetableType) => + await SendRequestAsync(new() + { + ["schet"] = GetCurrentSemesterId(), + ["type_z"] = ((int)timetableType).ToString(), + ["group"] = groupId.ToString() + }); + + public async Task GetSemesterStartDateAsync(int groupId) + { + using HttpClient client = new(); + string content = await client.GetStringAsync($"https://www.sut.ru/studentu/raspisanie/raspisanie-zanyatiy-studentov-ochnoy-i-vecherney-form-obucheniya?group={groupId}"); + + using IHtmlDocument doc = new HtmlParser().ParseDocument(content); + string labelText = doc.QuerySelector("a#rasp-prev + div:nth-child(2) > span")!.TextContent; + int weekNumber = int.Parse(ParserUtils.NumberRegex().Match(labelText).Value); + DateTime currentDate = DateTime.Today; + currentDate = currentDate + .AddDays(-(int)currentDate.DayOfWeek + 1) // Move to Monday + .AddDays(-7 * (weekNumber - 1)); // Move back to the first week + + return currentDate; + } + + private static Dictionary ParseListResponse(string responseContent) => + responseContent + .Split(';', StringSplitOptions.RemoveEmptyEntries) + .Select(item => item.Split(',')) + .ToDictionary( + parts => int.Parse(parts[0]), + parts => parts[1] + ); + + public async Task SendRequestAsync(Dictionary formData) + { + HttpRequestMessage request = new(HttpMethod.Post, "https://cabinet.sut.ru/raspisanie_all_new.php") + { + Content = new FormUrlEncodedContent(formData) + }; + + using HttpClient client = new(new HttpClientHandler + { + // Sometimes Bonch being Bonch just doesn't renew its SSL certificates properly + ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true + }); + + HttpResponseMessage response = await client.SendAsync(request); + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadAsStringAsync(); + } + + private static string GetCurrentSemesterId() + { + DateTime now = DateTime.Today; + int currentSemester = now.Month is >= 8 or < 2 + ? 1 // August through January - first semester + : 2; // Everything else - second + + int termStartYear = now.Year - 2000; // We need only last two digits (e.g. 25 for 2025) + + if (now.Month < 8) // Before August means we are in the second semester of the previous academic year + termStartYear--; + + return $"205.{termStartYear}{termStartYear + 1}/{currentSemester}"; + } +} diff --git a/api/Services/ParsingService.cs b/api/Services/ParsingService.cs new file mode 100644 index 0000000..8a9c9e2 --- /dev/null +++ b/api/Services/ParsingService.cs @@ -0,0 +1,138 @@ +using System.Globalization; +using System.Text.RegularExpressions; +using AngleSharp.Dom; +using AngleSharp.Html.Dom; +using AngleSharp.Html.Parser; +using BonchCalendar.Utils; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; + +namespace BonchCalendar.Services; + +public partial class ParsingService +{ + public CalendarEvent[] ParseGeneralTimetable(string rawHtml, DateTime semesterStartDate, string groupName) + { + using IHtmlDocument doc = new HtmlParser().ParseDocument(rawHtml); + + IHtmlCollection rawClasses = doc.QuerySelectorAll(".pair"); + List classes = []; + + foreach (IElement classItem in rawClasses) + { + var (className, classType, professors, auditorium) = ParseBaseInfo(classItem); + int weekday = int.Parse(classItem.GetAttribute("weekday")!); + + string timeLabelText = classItem.ParentElement!.ParentElement!.Children[0].TextContent; + Match timeMatch = ParserUtils.TimeLabelRegex().Match(timeLabelText); + string number = timeMatch.Success ? timeMatch.Groups["number"].Value : timeLabelText; + (TimeSpan startTime, TimeSpan endTime) = !timeMatch.Success ? + ParserUtils.GetTimesFromLabel(timeLabelText) : + ( + TimeSpan.Parse(timeMatch.Groups["start"].Value), + TimeSpan.Parse(timeMatch.Groups["end"].Value) + ); + + int[] weeks = [ + .. ParserUtils.NumberRegex().Matches(classItem.QuerySelector(".weeks")!.TextContent) + .Select(i => int.Parse(i.Value)) + ]; + + foreach (int week in weeks) + { + DateTime classDate = semesterStartDate + .AddDays((week - 1) * 7) // Move to the correct week + .AddDays(weekday - 1); // Move to the correct weekday + + classes.Add(GetEvent( + $"{number}. {className} ({classType})", auditorium, + GetDescription(groupName, professors, auditorium, weeks), + classDate, startTime, endTime)); + } + } + + return [.. classes]; + } + + public CalendarEvent[] ParseExamTimetable(string rawHtml, string groupName) + { + using IHtmlDocument doc = new HtmlParser().ParseDocument(rawHtml); + + IHtmlCollection rawClasses = doc.QuerySelectorAll(".pair"); + List classes = new(rawClasses.Count); + + foreach (IElement classItem in rawClasses) + { + var (className, classType, professors, auditorium) = ParseBaseInfo(classItem); + + DateTime classDate = DateTime.Parse(classItem.Children[0].ChildNodes[0].TextContent, CultureInfo.GetCultureInfo("ru-RU")); + Match timeMatch = ParserUtils.ExamTimeRegex().Match(classItem.GetAttribute("pair")!); + + if (!timeMatch.Success) + timeMatch = ParserUtils.ExamTimeAltRegex().Match(classItem.GetAttribute("pair")!); + + string number = timeMatch.Groups["number"].Success ? + $"{timeMatch.Groups["number"].Value}. " : string.Empty; + + TimeSpan startTime = TimeSpan.Parse(timeMatch.Groups["start"].Value.Replace('.', ':')); + TimeSpan endTime = TimeSpan.Parse(timeMatch.Groups["end"].Value.Replace('.', ':')); + + classes.Add(GetEvent( + $"{number}{className} ({classType})", auditorium, + GetDescription(groupName, professors, auditorium), + classDate, startTime, endTime)); + } + + return [.. classes]; + } + + private static CalendarEvent GetEvent(string title, string auditorium, string description, DateTime date, TimeSpan startTime, TimeSpan endTime) => + new() + { + Summary = title, + Description = description, + Start = new CalDateTime(date.Add(startTime - TimeSpan.FromHours(3)).ToUniversalTime()), + End = new CalDateTime(date.Add(endTime - TimeSpan.FromHours(3)).ToUniversalTime()), + Location = auditorium + }; + + private static string GetDescription(string groupName, string[] professors, string auditorium, int[]? weeks = null) + { + string str = $""" + Группа: {groupName} + Преподаватель(и): + - {string.Join("\n- ", professors)} + """; + + if (weeks is not null && weeks.Length > 0) + str += $"\nНедели: {string.Join(", ", weeks)}"; + + Match auditoriumMatch = ParserUtils.AuditoriumRegex().Match(auditorium); + + if (!auditoriumMatch.Success) + auditoriumMatch = ParserUtils.AuditoriumAltRegex().Match(auditorium); + + if (auditoriumMatch.Success) + str += "\n\n" + $""" + ГУТ.Навигатор: + https://nav.sut.ru/?cab=k{auditoriumMatch.Groups["wing"].Value}-{auditoriumMatch.Groups["room"].Value} + """; + + return str; + } + + private static (string className, string classType, string[] professors, string auditorium) ParseBaseInfo(IElement classElement) + { + string className = classElement.QuerySelector(".subect")!.TextContent; + string classType = classElement.QuerySelector(".type")!.TextContent + .Replace("(", string.Empty).Replace(")", string.Empty).Trim(); + + string[] professors = classElement.QuerySelector(".teacher[title]")!.GetAttribute("title") + !.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + string auditorium = classElement.QuerySelector(".aud")!.TextContent + .Replace("ауд.:", string.Empty).Replace(';', ',').Trim(); + + return (className, classType, professors, auditorium); + } +} diff --git a/api/TimetableType.cs b/api/TimetableType.cs new file mode 100644 index 0000000..dc0d68e --- /dev/null +++ b/api/TimetableType.cs @@ -0,0 +1,9 @@ +namespace BonchCalendar; + +public enum TimetableType +{ + Classes = 1, + Exams = 2, + ExamsForExtramural = 4, + Attestations = 14 +} diff --git a/api/Utils/NaasReasons.cs b/api/Utils/NaasReasons.cs new file mode 100644 index 0000000..33e2c60 --- /dev/null +++ b/api/Utils/NaasReasons.cs @@ -0,0 +1,1101 @@ +namespace BonchCalendar.Utils; + +/// +/// No-as-a-service rejection reasons. +/// +/// +/// Why? Because why not. +/// +public class NaasReasons +{ + /// + /// Get a reason for current error. + /// + public string GetReason() => + _reasons[new Random().Next(_reasons.Length)]; + + /* + * MIT License + * + * Copyright (c) 2025 Salman Qureshi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + // Original repo: https://github.com/hotheadhacker/no-as-a-service + + private readonly string[] _reasons = + [ + "In a different season of life, I might say yes\u2014but not right now.", + "While I appreciate it, pursuing this isn't something I can commit to.", + "Every time I think about it, I hear a tiny \u2018nope' in the wind.", + "My future self wrote me a note: \u2018Please don't do this again.'", + "It sounds interesting, but I'm not the best person for this.", + "I appreciate the thought, but it's not the right fit for me right now.", + "I'd love to say yes, but I just made a commitment to do less questionable things this week.", + "While I appreciate it, considering this isn't something I can commit to.", + "While I appreciate it, volunteering for this isn't something I can commit to.", + "I'm trying to be more intentional with what I agree to, and this doesn't quite fit.", + "I want to be honest with you\u2014I don't feel comfortable committing to this.", + "I have to trust my gut\u2014and it's gently suggesting a no.", + "Saying no to this helps me say yes to something that matters more to me.", + "If I said yes, I'd be lying to both of us\u2014and my therapist said to stop doing that.", + "I'm going to respectfully decline. It doesn't align with where I'm heading.", + "While I appreciate it, attempting this isn't something I can commit to.", + "This opportunity deserves more than I can give it right now.", + "I don't hate the idea\u2014but I also don't want to do it.", + "I'm not in a position to commit to this right now.", + "While I appreciate it, exploring this isn't something I can commit to.", + "I've made peace with declining things that don't energize me.", + "Even my calendar laughed when I tried to squeeze this in.", + "I've decided to hold off on new engagements for the time being.", + "This feels like something Future Me would yell at Present Me for agreeing to.", + "I've tried doing too much before, and this feels like d\u00e9j\u00e0 vu.", + "You've put great energy into this, and I hope you understand my decision to step back.", + "While I appreciate it, joining this isn't something I can commit to.", + "This is a great idea\u2014for someone else to take on.", + "The version of me who says yes to this doesn't exist anymore.", + "Even my coffee said, \u2018Not today.'", + "My inner peace committee vetoed this idea unanimously.", + "I know how much this matters, but I can't give it the attention it deserves.", + "I had a vision of doing this\u2014and then promptly fell asleep in it.", + "Mentally, I've already left the room where this would happen.", + "While I appreciate it, accepting this isn't something I can commit to.", + "This doesn't align with my values or current focus.", + "Please know that my decision isn't a reflection of you\u2014just my own boundaries.", + "I respect the opportunity, but I know my limits\u2014and this would stretch them too far.", + "If I took this on, I'd be betraying the time I promised myself.", + "I once made a similar decision and my cat hasn't looked me in the eye since. Let's not repeat history.", + "If I agree, I'd need to clone myself. And I don't trust him.", + "This one doesn't feel quite right, so I'll have to pass.", + "I'd rather say no with honesty than yes with resentment.", + "Thanks for thinking of me, but I'll have to pass on this one.", + "I've taken some time to reflect, and I believe passing on this is the right call for now.", + "I truly value our connection, and I hope my no doesn't change that.", + "It's not a no forever, but it's a no for now.", + "My enthusiasm is sincere, but my availability is imaginary.", + "While I appreciate it, embracing this isn't something I can commit to.", + "At this point, I don't have the bandwidth to do this justice.", + "I've learned to protect my time, and this doesn't align with how I want to spend it.", + "That's not something I can prioritize at the moment.", + "If I said yes, the universe might collapse from the shock.", + "If I agree, it could break the space-time continuum.", + "Helping you now could create a time paradox—better not risk it.", + "I can't, I'm busy preventing a future where I actually say yes.", + "My calendar is aligned with Jupiter, and the stars say no.", + "I'm pretty sure saying yes is forbidden in this galaxy.", + "The multiverse would never forgive me if I complied.", + "Any timeline where I say yes ends in chaos, so I'm choosing wisely.", + "Me agreeing? That's like dividing by zero—dangerous and not allowed.", + "Accepting that offer would cause a glitch in the Matrix.", + "I'm currently booked for the next decade in pretending to be productive.", + "I'd love to, but I'm swamped with counting the ceiling tiles.", + "Sorry, I'm too busy rearranging my sock drawer by color and mood.", + "I have a meeting with my couch and it won't take no for an answer.", + "I've got a calendar alert to sit and stare into space at that exact time.", + "My schedule is full: I'm alphabetizing my snacks.", + "I need to finish an intense session of doing nothing at all.", + "I've a prior commitment to binge-watching paint dry.", + "I'm in the middle of a very important procrastination marathon.", + "I can't; I'm attending the opening of my refrigerator.", + "I'd help, but my imaginary friend needs me more.", + "I can't; my pet rock is having an emotional crisis.", + "My goldfish is getting married that day, I have to attend.", + "I promised my imaginary cat I'd stay home tonight.", + "My guardian unicorn says I shouldn't.", + "I have to babysit my imaginary friend's imaginary kids.", + "Unfortunately, I'm volunteering as a taste-tester for my pet's new diet.", + "My pet dragon has a vet appointment, can't miss it.", + "I'm grounded by my pet parrot for bad behavior, so I can't go.", + "I lent my time machine to a friend and now I can't go back and free up this afternoon.", + "I've made a vow to avoid any form of productivity.", + "It's against my personal religion to do anything useful today.", + "My psychiatrist advised me to avoid potentially fun activities.", + "I swore an oath that I must decline all requests on a whim.", + "I only function on coffee and denial, and I'm out of coffee.", + "I'm allergic to any form of responsibility.", + "I have a doctor's note that specifically forbids me from helping.", + "I'd love to help, but I'm trying to break my streak of helpfulness.", + "My procrastination coach said I'm not ready for actual tasks yet.", + "I gave up saying yes for Lent, and it hasn't ended yet.", + "I have a personal policy of saying no on days ending in 'y'.", + "My phone's calendar just facepalmed at me for even considering it.", + "I have to update my social media status to 'busy not doing that'.", + "I'm currently trending towards 'no'.", + "I can't—I promised Netflix I'd stay in and watch movies.", + "My Wi-Fi of motivation has lost connection.", + "I'm waiting for my phone to recharge—can't do anything until it's at 100%.", + "My bio on Twitter says 'Often says no', gotta stay on brand.", + "I'm busy searching for motivation on Google; no results found.", + "I'm stuck in an internet rabbit hole and I can't find the exit.", + "I can't come; I'm on a Zoom call with my couch.", + "I would love to, but I'd rather not.", + "My answer is a no—freshly baked, just for you.", + "I've considered it thoroughly, and I'm going with a hard no.", + "Not even if there were free donuts.", + "This is the part where I say 'no' and we both move on.", + "Let me check my schedule... oh look, it's a permanent no.", + "I'm flattered you asked, but I'll pass with flying colors.", + "I've reached my quota of saying yes for the year.", + "I'm too cool to say yes to that.", + "As the saying goes, 'Nope, nope, nope.'", + "You don't want me to say yes—trust me, it's for the greater good.", + "The last time I said yes, it rained for 40 days. Not doing that again.", + "I promised my future self I'd say no to this one.", + "If I agreed, I'd have to explain it to my therapist.", + "My past experiences with yes have been traumatic.", + "Saying yes is on my list of things to quit.", + "I would, but then I'd have to admit I was wrong about hating everything.", + "The universe gave me a sign to say no (it was a neon sign, very clear).", + "I once said yes. It didn't end well. There were llamas involved.", + "My gut feeling and I unanimously voted no.", + "That sounds like an amazing opportunity... for someone else.", + "What a great idea! I'm sad I can't be part of it. Well, not that sad.", + "I envy the people who will say yes to you, truly.", + "I absolutely adore the concept, but I'll have to sit this one out.", + "Brilliant plan! Unfortunately, I'm in witness protection from good ideas.", + "That would be a dream come true. Too bad I'm awake and saying no.", + "You've painted such a lovely picture, I'll admire it from afar.", + "Such a generous offer! I'm going to generously decline.", + "I'm sure it'll be a resounding success, but I'll be a distant admirer.", + "Sounds thrilling! I'm already tired just thinking about it.", + "I've already changed into my pajamas, so it's a no.", + "My couch has declared a no-travel zone and I'm abiding.", + "I can't be bothered to change out of my comfortable blanket burrito.", + "I'm saving my energy for an epic nap.", + "I'm too lazy to even come up with a good excuse, so just no.", + "Moving from this spot requires 24-hour notice.", + "I'm conserving energy today for no particular reason.", + "I would, but that sounds like effort and I'm allergic.", + "I'm currently experiencing technical difficulties with moving.", + "I'm practicing for a marathon of not moving.", + "I have to wash my hair that decade.", + "I've got to defrost the iceberg in my freezer.", + "Sorry, I need to iron my underwear that night.", + "I must attend to my garden of plastic flowers.", + "My fish needs a walk and it takes priority.", + "I have to rotate the tires on my office chair.", + "I'm marathoning a series of doing household chores in my head.", + "The dishes threatened to revolt if I leave them again.", + "My fridge and I have couples therapy at that time.", + "I need to sit this one out while I sort my sock zoo.", + "In an alternate universe, I totally did that. Just not in this one.", + "Maybe in my next life I'll consider it, but current me says no.", + "In a parallel dimension I'm already on it—too bad we're here.", + "I consulted my alternate self; they also declined.", + "If I clone myself, I'll send the clone. Until then, no.", + "I checked with future me, and they rolled their eyes, so I'll pass.", + "I'm stuck in a time loop of saying no.", + "In an alternate timeline I said yes, and it caused a zombie apocalypse.", + "My evil twin might be interested; too bad they're busy too.", + "Somewhere in a parallel universe I'm more helpful. This isn't that universe.", + "I would, but that sounds dangerously close to work.", + "My desire to do that is currently in a coma.", + "I'm sure I'd mess it up spectacularly, so better not.", + "I could participate, but then who would hold up the wall over here?", + "That request is above my pay grade of zero.", + "The spirit is willing, but the schedule says nope.", + "I'd rather not tempt fate by trying something new today.", + "If I join, I'd just bring down the average.", + "Me doing that? That's the plot twist no one wants.", + "I'm practicing social distancing from that idea.", + "I have a condition that prevents me from saying yes (it's called sanity).", + "My doctor said I need more vitamin 'No'.", + "I just came down with a severe case of Not-Today-itis.", + "Mentally, I'm already on vacation.", + "I'm on bedrest until further notice, doctor's orders (my doctor is Dr. Seuss).", + "I've got chronic commitment-phobia.", + "I'm coming down with something; it's called laziness.", + "I have an appointment with a pillow and a blanket.", + "My therapist said I should avoid things that make me miserable; I assume this qualifies.", + "I'm having a 'me' time emergency.", + "I have to save the world from boredom by staying home.", + "I'm in the middle of negotiating world peace (in a video game).", + "My superhero alter ego needs a day off, and so do I.", + "I'm on a secret mission to avoid anything you just asked.", + "I'd love to, but then who will save the world while I'm gone?", + "I'm busy orchestrating a global nap time.", + "I have to keep the couch from floating away, it's an important job.", + "The fate of the potato chip I'm about to eat is in my hands, I can't abandon it.", + "I have been chosen as the guardian of my living room today.", + "I'm fighting an invisible dragon at home. It's taking longer than expected.", + "You really don't want me on this – my specialty is ruining things.", + "I'm like a black cat of projects; it's safer if I stay away.", + "Trust me, I'm doing you a favor by saying no.", + "My participation comes with a warning label, so I'll spare you.", + "I'd join, but I'm only here to provide witty refusals.", + "I'm actually terrible at that, unless you need an example of how not to do it.", + "You deserve someone who will say yes without giving three excuses first.", + "My track record suggests I should sit this one out.", + "I'm the wrong person for the job; I'm the right person for saying no.", + "If you need someone to sabotage it, I'd be perfect. Otherwise, I'll pass.", + "The fortune cookie said 'No' just last night.", + "The prophecy foretold I would decline this.", + "My tarot cards all just collectively sighed.", + "I checked my crystal ball, and it said 'nah'.", + "The spirit world recommends I stay home.", + "I threw some runes and they spelled out N-O.", + "My magic 8-ball said 'Outlook not so good'.", + "I was reading tea leaves and they said to avoid this at all costs.", + "A psychic warned me about requests like these.", + "I'm in a committed relationship with my bed at that time.", + "Sleep and I have a standing date, can't break it.", + "I would, but I'm trying this new thing called sleeping.", + "I already took a melatonin. It's lights out for me.", + "I'm saving all my energy to blink and breathe.", + "I have an early morning of sleeping in.", + "If it's not a nap, I'm not interested.", + "I can't; my pillow needs me.", + "Count me out, I'm counting sheep instead.", + "I'll be in the middle of my power nap (which might last all day).", + "Disclaimer: Any agreement on my part is void where fun is prohibited.", + "Fine print: My 'yes' comes with 0% enthusiasm.", + "I checked the terms and conditions of my life, and it says 'nope'.", + "By agreeing, I might violate several personal bylaws.", + "According to the unwritten rules I just wrote, I must decline.", + "My user agreement strictly prohibits me from doing that.", + "I would violate the Geneva Convention of Introverts if I agreed.", + "I ran this request through my internal compliance, and it failed.", + "My lawyer (who is imaginary) advises against it.", + "The contract I signed with my bed doesn't allow me to do anything else.", + "My plate is full, and I already ate the portion of doing favors.", + "I'm overbooked, overwhelmed, and overjoyed to say no.", + "My schedule spontaneously combusted from too many tasks.", + "I'm currently managing too many crises—adding another might cause a meltdown.", + "I have too much on my plate, and I'm on a no-carb diet (no extra tasks).", + "I'm drowning in tasks, and I didn't bring a snorkel.", + "I would, but I'm already in over my head with other regrets.", + "My to-do list and I aren't on speaking terms, it's so long.", + "I'm way too popular with tasks I haven't done yet.", + "The hamster running my life wheel is on strike due to overwork.", + "'No' is just 'on' spelled backwards, and I'm off.", + "Spell check suggests I say N-O.", + "I wrote you a poem: Roses are red, violets are blue, my answer is no, no can do.", + "The only thing I'm inclined to say is a decline.", + "Let me put it in a language you understand: No.", + "I'm fluent in 5 languages, and in all of them, the answer is no.", + "'Yes' and I aren't on speaking terms at the moment.", + "I would respond with a fancy Latin phrase for no, but plain 'no' will suffice.", + "As an acronym, my answer is N.O.", + "If you rearrange the letters of 'yes', you get 'sye'—which is exactly how much sense it would make for me to do this.", + "I'm booked until the heat death of the universe.", + "My calendar is filled up through the next century, sorry.", + "I have a scheduling conflict with the rest of my life.", + "I'm not free until the sun rises in the west and sets in the east.", + "Check back with me in a million years, I might have time then.", + "I'm on hold with life right now; expect a long wait.", + "My availability is currently in a state of quantum uncertainty, leaning towards no.", + "I've pre-booked my next thousand refusals; you're just one of them.", + "My schedule is tighter than my jeans after the holidays.", + "I looked at my calendar, and it laughed at me for even asking.", + "Thank you for thinking of me, but I'm going to decline hilariously.", + "I appreciate the offer, but I'll have to decline, mainly out of laziness.", + "Much obliged that you asked, but no can do, buckaroo.", + "I'm honored you thought of me, but I must respectfully and comically decline.", + "Please accept my sincerest apologies as I laugh and say no.", + "Regretfully, I'll have to pass, but I'll do it with a smile.", + "It's not you, it's me. I'm just allergic to doing things.", + "Respectfully, I'm going to ghost this opportunity.", + "I'm truly touched you asked. Truly. Still no, though.", + "With all due respect, no way, José.", + "I forgot how to say yes.", + "I have short-term memory loss when it comes to responsibilities.", + "I'd agree, but I just forgot what you asked, so... no.", + "My brain auto-deletes requests like that.", + "I've got selective amnesia for favors.", + "I was going to help, but I forgot to care.", + "I'm sorry, I suffer from CRS (Can't Remember Saying yes).", + "I can't remember the last time I said yes, and I won't start now.", + "What was that? Did you ask something? No? Great.", + "I put your request in my mental shredder.", + "I can't; I'm busy teaching my goldfish to play fetch for charity.", + "I'm devoting that time slot to contemplating the meaning of life. It's a charity case really.", + "I've donated all my free time to non-doing causes.", + "I'm working on a petition to ban requests like this.", + "My calendar is filled with volunteer work at the Society of Professional Naysayers.", + "I'm training for a charity event: a marathon of saying no.", + "I'm raising awareness for the endangered word 'no' by using it frequently.", + "I auctioned off my time slot for that day to the highest bidder of nothing.", + "I'm doing a silent retreat away from responsibilities.", + "I gave all my free time to someone else; I'm running a deficit.", + "I'm on a strict no-social-interaction diet.", + "I have to cancel; I've exceeded my people-ing quota for the week.", + "I'm introverting so hard right now, I can't stop to do that.", + "My social battery is at 0%, and my charger is missing.", + "I'm away on an introvert adventure (indoors, alone).", + "I can't come out; I've recently taken up hermit life.", + "Socializing is off the table, literally, I flipped the table.", + "I'm allergic to crowds, and even one person counts sometimes.", + "I'm practicing social distancing... from everyone.", + "Public me is out of office; please contact me never.", + "I accidentally glued myself to the couch.", + "My legs have filed a complaint about excessive walking.", + "My get-up-and-go got up and went without me.", + "I'm having an out-of-body experience and I'm not back yet.", + "My energy and I are currently in a long-distance relationship.", + "I woke up with a serious case of gravity today; can't get up.", + "I have an acute case of Couch Magnetism.", + "My limbs have gone on strike until further notice.", + "I'm feeling as active as a sloth on vacation.", + "I'm under house arrest by my own laziness.", + "I'd rather bathe in week-old soup than do that.", + "I prefer to juggle chainsaws blindfolded—seems safer than saying yes.", + "I would climb Mount Everest barefoot before I'd agree to that.", + "I'd rather be the sole target in a paintball match than participate.", + "Honestly, chewing glass sounds more fun than saying yes to this.", + "I'd rather do my own dental surgery with no anesthesia.", + "Saying yes? I'd sooner challenge a kangaroo to a boxing match.", + "I'd rather walk on Lego bricks for an hour straight.", + "I prefer the thrill of not doing it, thanks.", + "I'd accept, but I have a strict policy of not walking into obvious traps.", + "I would, but my bank account said I can't afford the gas to get there.", + "Unless there's a cash prize for me, I'll have to decline.", + "I only work on commission, and this request doesn't pay in pizza.", + "If you pay me in gold bars, I'll reconsider. Otherwise, no.", + "My accountant advised me that saying yes doesn't balance my books.", + "I'm waiting for a rich benefactor to fund my yeses.", + "I can't fit that into my budget of time and energy.", + "I'm in a bit of a financial bind: low on funds and on yeses.", + "Unless I get stock options for this, I'm out.", + "I require a down payment just to think about saying yes.", + "I'm waiting for a sign from the universe... oh, there it is: it says no.", + "I need to conserve my remaining brain cell.", + "My existential dread and I have plans.", + "I'm on strike until further notice, and I'm the union leader.", + "I've got a hot date with a pizza and a TV show.", + "I'm currently observing National No Day, it's a personal holiday.", + "I have a policy to not leap into volcanoes, and this feels similarly dangerous.", + "Can't come, I've made other plans with my bed and pillow.", + "I must decline, for science!", + "I'm trying to see how long I can go without doing anything productive.", + "My mom said I have to stay home and, um, alphabetize the pantry.", + "My family booked me for a game of hide-and-seek and I'm the missing piece.", + "My kids grounded me for trying to have a life.", + "I can't; my ancestors' spirits would be displeased if I did.", + "My dog will only let me out of the house if it's for walks, not work.", + "My spouse already made dinner plans with me and Netflix.", + "It's family night and by family, I mean me and my snacks.", + "I have to attend a family tradition of doing nothing tonight.", + "My cat is giving me the 'don't you dare go out' look.", + "My goldfish family reunion is happening in my fish tank.", + "NO is just YES suffering from oppositional defiant disorder.", + "N-O: two letters that, when combined, describe my plans perfectly.", + "I'm going to turn that invitation into an inviti-NOPE.", + "'No' is my spirit animal.", + "I can't spell 'nope' without N-O.", + "They say double negatives make a positive, but my single negative stands firm.", + "I'm consonantly refusing and vowel-ing out of this one.", + "Just in case you missed it: N as in No, O as in Oh-no-not-today.", + "My motto is 'Just say no', and I'm very loyal to my motto.", + "I signed up for a course on saying no, and I'm just practicing my homework.", + "I prefer to watch the grass grow at that time, sorry.", + "Actually, I was planning to count sand grains, which sounds more fun.", + "I've been looking forward to reorganizing my button collection instead.", + "I scheduled some quality time with my ceiling fan.", + "I have a thrilling date with doing absolutely nothing.", + "I'm engaged in a passionate affair with free time, and I can't cheat on it.", + "I have front row seats to my couch and I intend to use them.", + "There's a rerun of a show I've never seen that I just can't miss.", + "I already promised to spend that time twiddling my thumbs.", + "I have to decline; I wouldn't want to outshine everyone with my presence.", + "My greatness is just too much for that event to handle.", + "I'd say yes, but then it wouldn't be fair to the others when I excel effortlessly.", + "I can't make it; the paparazzi might swarm your event if I do.", + "I'm doing you a favor by not coming and stealing the spotlight.", + "I would attend, but I'm kind of a big deal in my own mirror.", + "I'm too cool for that (and by too cool, I mean too lazy).", + "Allow me to gracefully decline, so you can have fun without my overwhelming awesomeness.", + "I'll have to decline; I'm too busy being fabulous elsewhere.", + "I must humbly decline because my perfection is exhausting.", + "I have to return the One Ring to Mordor that day.", + "Sorry, I'm scheduled to fight Darth Vader then.", + "I need to sit by the phone in case Hogwarts calls.", + "I would but I've already volunteered as tribute elsewhere.", + "My Bat-Signal just lit up, gotta go save Gotham instead.", + "I can't—my flux capacitor is on the fritz and I can't make it in time.", + "That night is my scheduled trip back to the future.", + "Zombies may attack if I leave the house, I saw it in a documentary (okay, it was a zombie movie).", + "I'm busy negotiating with some minions; world domination can't wait.", + "I have to consult Doctor Strange about attending (he foresaw that I don't).", + "My boss told me to only say yes to salary raises, so no to everything else.", + "I've used up all my sick days, so I'm calling in well and staying home.", + "My teacher said I need to focus on not overcommitting.", + "I have a work thing... it's called avoiding work.", + "My calendar at work has a big red 'NOPE' on that date.", + "I can't, I have an important meeting to stare at a spreadsheet and contemplate life.", + "I'm taking a personal day to avoid persons.", + "Got a conference call with myself, and it's very time-consuming.", + "My overtime got into a fight with my free time, and free time lost.", + "My PTO (Permission To Opt-out) is in effect.", + "If I go, it might trigger the apocalypse. Better not risk it.", + "I have to stay put to prevent the zombie outbreak from starting (long story).", + "I'd come, but Nostradamus predicted I'd say no.", + "It's one of the signs of the apocalypse if I say yes, let's avoid that.", + "My attendance is the last seal before the apocalypse, so hard pass.", + "I fear my yes would summon Cthulhu or something.", + "I'm avoiding causing a butterfly effect that ends the world.", + "Legend says if I ever agree, the world will end in a spectacular musical number.", + "I made a deal with a demon to never say yes, and I hate to break a contract.", + "The doomsday clock ticks faster every time I consider agreeing.", + "Yes, you may interpret my silence as a no.", + "I'm nodding yes in spirit, but physically I'm shaking my head no.", + "Sure...ly you can find someone else?", + "Yes is in my vocabulary, just not today.", + "On a scale of yes to no, I'm at a 'not in a million years'.", + "I meant to say yes, but all that came out was no.", + "Consider my 'yes' lost in the mail.", + "Y-E-S are letters you'll not hear from me today.", + "Picture me enthusiastically not doing that.", + "Imagining me helping? Now imagine the opposite. There you go.", + "I'm feeling unlucky, so I better not risk saying yes.", + "I rolled the dice and it came up 'no'.", + "I consulted Lady Luck; she told me to stay home.", + "My luck ran out just before you asked.", + "If I had a lucky penny for every time I said no, I'd be rich. Speaking of which, no.", + "I'd join but I accidentally walked under a ladder and broke a mirror. Not chancing it.", + "My fortune teller said 'not in your stars', I trust that.", + "I pulled a card from a deck; it said 'Do Not Pass Go, Do Not Say Yes'.", + "Fate decided to swipe left on this one.", + "The stars aligned... to form a big 'NO'.", + "I can't; the weather forecast said there's a 100% chance of me staying home.", + "It's raining 'no's and I'm soaking wet in them.", + "I only go out in perfect weather conditions, which this clearly isn't (for me).", + "I'm experiencing a heavy downpour of laziness.", + "The winds of fate blew and I got hit in the face with a 'no'.", + "There's a storm brewing and it's named My Reluctance.", + "I suspect a sudden tornado of responsibilities will keep me inside.", + "I'm under the weather, specifically a cloud of nope.", + "The forecast for that day is me with a high chance of not showing up.", + "I'd hate to step out and melt; I'm made of sugar and sarcasm.", + "I could say yes, but that would be a lie.", + "My heart says yes, but my soul says 'are you kidding?'", + "I'm physically capable of doing it, just morally opposed (to effort).", + "I have an inner conflict: Part of me says 'no'. The other part also says 'no'.", + "I've reached an age where I just say no to things I hate. And I hate this.", + "My willpower to do that got stuck in traffic and won't make it.", + "I gave it some thought. The thought was 'no'.", + "In theory I could, but let's stick to reality where I won't.", + "I'm trying to be less of a people-pleaser, starting now.", + "I just looked in the mirror and practiced saying 'no' and it felt right.", + "I can't say yes before I've had my coffee. And I never plan to have coffee.", + "No caffeine, no can-do.", + "My coffee and I have a pact: no important decisions without it, and it's not available.", + "I would say yes, but I haven't met my daily coffee quota to deal with the consequences.", + "I'm in a decaffeinated state of no.", + "My brain is only powered by caffeine, and we're experiencing a power outage.", + "No java, no java-do.", + "The coffee machine called in sick, so I'm incapable of agreeing to anything.", + "I promised myself I'd only decide things after coffee; too bad it's already no-o'clock.", + "My morning coffee and I decided this isn't worth the caffeine it would take.", + "I'm too busy plotting world domination to attend.", + "If I said yes, it might ruin my evil plan.", + "My secret lair needs tending to at that time.", + "I'm in the middle of a devious scheme and can't break away.", + "I can't help you; I'm a supervillain in training and this would mess up my cred.", + "I would join, but then I'd have to recruit you into my plans and you wouldn't like that.", + "I'm brainstorming how to take over the world, and I need full concentration.", + "My minions scheduled a revolt for that day, and I need to be present.", + "I must regretfully decline as I have many diabolical laughs to practice.", + "Evil never sleeps, and apparently neither do I, except I'm definitely not going out.", + "I'm trying to align my chakras by not stressing, so I'll say no.", + "To maintain balance in the Force, I must decline.", + "My yin and yang are telling me not to do it.", + "For the sake of cosmic balance, I can't agree.", + "I'm in the middle of a feng shui adjustment to my life that forbids new commitments.", + "I must refuse, or the delicate equilibrium of my laziness will be disrupted.", + "Saying yes would throw off the balance of my weekly yes-to-no ratio.", + "I need to keep my karma clear, and I sense a yes would muddy it.", + "I must achieve inner peace, which means not complicating my day with this.", + "I'm afraid if I enjoy myself, it will become a bad habit.", + "If I end up liking it, then I'll have to do it again, so no.", + "I'm scared of success, so I must decline anything that might work out.", + "I'm afraid I'll set the bar too high if I say yes and do a good job.", + "Fun? I'm not sure I remember what that is, better not risk it.", + "I'm allergic to fun—break out in sarcasm.", + "I avoid anything that might make me smile too much.", + "I'm trying to remain mysterious and saying yes would make me too accessible.", + "I like to keep my expectations low by not doing anything.", + "I'm avoiding fun until further notice.", + "Asking me to do that is like asking a fish to climb a tree.", + "Me doing that would be like a chicken trying to do algebra.", + "Saying yes would be as unusual as a cat becoming a vegan.", + "That idea and I go together like oil and water.", + "Me participating is like a square peg in a round hole—just not fitting.", + "My motivation and that task are like two magnets repelling each other.", + "It would be like expecting a sloth to win a 100m dash.", + "As compatible as ice cream and hot soup—best kept separate.", + "Me saying yes is about as likely as pigs piloting a spaceship.", + "That request is the peanut butter to my allergy.", + "I just realized it's a holiday: National Not Gonna Do It Day.", + "I'm observing a personal holiday called Nope-vember.", + "I can't; I've got Festivus grievances to air that day.", + "I'm booked that day celebrating the Festival of No.", + "I'm already committed to the Anti-Work Holiday.", + "I celebrate Opposite Day by doing the opposite of what you ask.", + "Sorry, it's my annual day of rest... like the Sabbath, but for laziness.", + "I only celebrate events that involve cake, so unless there's cake, no.", + "That date conflicts with my extended celebration of Chillmas.", + "It's my pet rock's birthday, which is basically a global holiday to me.", + "I want to say yes, but my common sense is yelling 'no' repeatedly.", + "My heart says maybe, but my brain threw a giant red flag.", + "My gut feeling started doing the Macarena when you asked (that's a no dance).", + "Every fiber of my being is currently doing the wave in honor of saying no.", + "The tiny responsible voice in my head and the loud lazy one both agree: no.", + "I asked my better judgment, and it laughed hysterically.", + "My brain highlighted that request and hit the delete key.", + "My conscience would haunt me if I said yes.", + "Even the angel on my shoulder is like, 'nah, dude'.", + "My inner voice is just screaming 'NOOOO' Darth Vader-style.", + "I'm not procrastinating; I'm proactively saying no in advance.", + "I'm delaying my decision until the end of time—spoiler: it's no.", + "Why do today what I can avoid indefinitely?", + "My planner just says 'Nope' on every page this week.", + "I'll put this request on my to-do list right after 'learn teleportation'.", + "I'm in the process of rescheduling my procrastination for later.", + "I'm waiting for the last minute, and it hasn't arrived for this.", + "I thought about doing it later, then realized later is fully booked as well.", + "I'm on a procrastination roll, can't stop now.", + "I'm deferring my yes until a year that doesn't exist.", + "I would help, but then you'd have two of us needing help.", + "I'm not sure you can afford the damage I'd do by trying.", + "My help comes with a money-back guarantee, but you don't want it.", + "I'm the last person who should do that—literally, I'm last in line.", + "I tried doing something similar once; let's just say the fire department got involved.", + "If I do it, you'll spend more time fixing my mistakes.", + "My competence is on vacation right now.", + "I excel at procrastinating, not at actually doing things.", + "I'd volunteer, but the incompetence fee I charge is quite high.", + "I would assist, but then I'd have to legally disclaim any results.", + "It doesn't really spark joy in me, so I'll pass.", + "That idea doesn't pass the vibe check for me.", + "I'm not feeling a strong gravitational pull towards doing that.", + "My interest level is somewhere between zero and negative.", + "If I had any less interest, I'd be in a coma.", + "My curiosity just left the room when I heard that idea.", + "I'm sure it's fascinating, but my attention span thinks otherwise.", + "I'm saving my enthusiasm for something duller.", + "I fell asleep halfway through considering it.", + "I tried to get excited about it and pulled a muscle.", + "The government has me in a witness protection program against events like this.", + "My parole officer said I'm only allowed to work on being lazy.", + "The FBI called; they said me saying yes would be a national security risk.", + "I would come, but the judge of my life sentenced me to staying home.", + "My license to have fun was revoked.", + "I'd love to comply, but I'm under strict orders from myself not to.", + "The Queen of England ghost-told me to stay home (I only take royal commands).", + "The island I'm exiled on doesn't allow me to leave for events.", + "I'm contractually obligated to decline under the terms of my life choices.", + "The police told me I'm too interesting to attend normal gatherings.", + "I'm too old for this kind of commitment to things.", + "I think I'm at an age where I just say no to stuff like this.", + "At my age, my give-a-darn is busted.", + "I'm having a midlife crisis and saying yes isn't part of it.", + "I'm in my rebellious phase of only doing what I want, so no.", + "I'm channeling my inner grumpy old person and staying home.", + "I'm revisiting my teenage rebellion by saying no to everything.", + "In my day, we didn't have to do things we didn't want to (and I'm keeping it that way).", + "I'm practicing for my future as a crotchety old hermit.", + "I'm aging like fine wine, which means getting more stubborn about saying no.", + "My fairy godmother said I should stay home or I'll turn into a pumpkin.", + "Cinderella left one shoe here and I need to help find the other instead of going out.", + "I'm awaiting Prince Charming with pizza, so I can't go anywhere.", + "I can't come; the Big Bad Wolf is at my door (selling insurance, apparently).", + "I have to keep an eye on my gingerbread house, you know how it is.", + "The seven dwarves warned me about overextending myself.", + "I'm under a sleeping spell that only time (and not doing that) can break.", + "I lost my glass slipper and can't go out without proper footwear.", + "The frog I was supposed to kiss to become a prince gave me a rain check.", + "I'm stuck in a fairy tale, and agreeing to that isn't part of the plot.", + "I have to guard the refrigerator tonight; snacks are counting on me.", + "I've dedicated that time to an ice cream in need of company.", + "I'm carb loading for a marathon of not doing that.", + "I have a dinner date with a pizza and it would be jealous if I cancel.", + "I can't go out; my fridge just opened a bottle of wine and it'd be rude not to join.", + "I'm testing a theory that I can survive on chips alone, can't interrupt that.", + "My oven and I have plans to bake and eat an entire cake.", + "I promised to help my popcorn pop—it's a very involved process.", + "I would, but I just started cooking a five-course meal for one (me).", + "I'm learning to cook invisible meals; it's very time-consuming.", + "I have to be on standby in case Netflix needs me to watch something.", + "I'm on call as the family couch potato.", + "I'm the designated stay-at-home person this week.", + "I'm serving jury duty in the court of my own laziness.", + "I'm on a secret standby mission – can't disclose, just know I can't come.", + "I'm the emergency contact for my TV, so I must remain available.", + "I'm committed to staying home in case of a snack emergency.", + "I signed up to be a test subject for sleeping in, and I have a session then.", + "I'm part of a top-secret project called Project No.", + "I have a duty to uphold: keep my couch from feeling lonely.", + "My car decided to take a personal day off.", + "The public transportation in my living room is currently not operational.", + "I tried to leave, but my door wouldn't let me (we negotiated, and I stayed).", + "My GPS recommended I stay home to avoid bad decisions.", + "My carpool pony is sick. Yes, it's a pony, don't ask.", + "I would bike there, but my bike just laughed at me.", + "Uber doesn't deliver me to bad ideas.", + "I missed the train of thought that would make me go.", + "My broomstick is in the shop (you know how unreliable those are).", + "All roads lead to me staying home.", + "My insurance doesn't cover acts of me doing that.", + "I need to stay in a safe environment, and that event sounds risky (to my sanity).", + "Occupational Health and Safety flagged that activity as a hazard for me.", + "I'm waiting for my bubble wrap suit to arrive before I attempt such things.", + "That sounds dangerous for my well-being (mostly my mental well-being).", + "I only operate heavy machinery (like my couch) under supervision, so I can't go.", + "The last time I tried, I got a papercut on my soul.", + "For safety reasons, I'm required to stay away from that kind of excitement.", + "My guardian angel submitted a restraining order against that plan.", + "I'd prefer not to injure my will to live by saying yes.", + "I've transcended the need for activities like that.", + "I'm focusing on the journey of doing nothing, not the destination of that task.", + "My existential crisis and I decided against it.", + "In the grand tapestry of life, I'm the thread that sits this one out.", + "I've realized the meaning of life isn't doing that, so I'm good.", + "I'd rather search for the purpose of life than attend.", + "I think, therefore I am... not going to do that.", + "My current philosophy: if it requires pants, it's a no.", + "I'm practicing wu wei (doing nothing) as per Taoism, so I must decline.", + "To do or not to do? That is the question, and I've answered 'not to do.'", + "I trained for years under a master of saying no.", + "I got a black belt in No-jitsu.", + "I've been preparing my whole life to say no to this exact request.", + "I took a seminar on assertiveness and I'm itching to use what I learned: No.", + "My reflexes are honed to automatically decline invites.", + "I signed up for a 'Just Say No' workshop and I need to practice.", + "I'm part of a secret society where we only communicate via refusals.", + "My knee-jerk reaction is no, and my knee is feeling particularly jerky.", + "Saying no is my cardio.", + "I've been rehearsing my 'no' all week for just such an occasion.", + "No is my middle name. Well, not legally, but spiritually.", + "I come from a long line of expert decliners.", + "My DNA test came back: I'm 100% not doing that.", + "I'm genetically predisposed to say no.", + "I was born to say no—first word I ever said and never stopped.", + "I have a rare genetic disorder where I break out in refusals.", + "My spirit animal is a sloth, and it's telling me to stay put.", + "Astrologically, I'm a No-rgo or a Libra-thinks-not (one of those).", + "I took an oath as a kid to avoid anything resembling this.", + "My life coach advised me to eliminate stressors, starting with this.", + "My friends would have a heart attack if I suddenly said yes to things.", + "It would set a bad precedent if I started agreeing to reasonable requests.", + "If I start saying yes now, where will it end? World domination? Better not.", + "Saying yes once might give me a reputation for being helpful—can't have that.", + "I can't shock my system with responsibility like that; doctor's orders.", + "Yes is such a small word for such a life-changing event, I'm not ready.", + "I'm afraid saying yes now will open floodgates of expectations.", + "If I said yes, people might expect me to do it again. Perish the thought.", + "Agreeing to this would confuse everyone who knows me as a professional avoider.", + "My brand is 'reliable disappointment', and I have to stay consistent.", + "Can I take a rain check and then lose it forever?", + "Let's pencil it in for the 12th of Never.", + "I'll join you on the 32nd of this month.", + "How about I catch the next one... in a parallel universe?", + "Maybe another time, like when pigs fly or hell freezes over, whichever comes first.", + "I'll attend in spirit. Specifically, the spirit of 'not there'.", + "Let's say I was there in our imaginations and call it a day.", + "I'm there in thought, just not in body or actual presence.", + "I'll let you know when I'm free. (Don't wait up.)", + "I'm on a mission in a video game and real life can't compete.", + "I would, but I'm one XP away from leveling up in doing nothing.", + "My guild in World of Warcraft needs me to farm 'No' potions.", + "I'm in the middle of a high-stakes Tetris game (yes, it's very serious).", + "I promised my online friends I'd keep the server alive by staying on.", + "I'm practicing my gaming skills; real life quests have to wait.", + "I have to defend my virtual village from pixelated invaders at that time.", + "My controller died, and so did my desire to go out.", + "I'm roleplaying as a recluse currently.", + "I'm busy achieving a new high score in introversion.", + "I have nothing to wear that would match the look of regret I'd have if I went.", + "All my clothes are allergic to outside activities.", + "My wardrobe and I have an agreement: it only sees the couch.", + "I would go, but I've already changed into my pajamas.", + "I can't find my motivation to get dressed up; I think I donated it.", + "The dress code for that sounds like 'effort', so I'm out.", + "I only attend events that allow pajamas as formal attire.", + "My fashion sense just texted: it said not to bother.", + "I'm stuck in my closet's Narnia and can't get out to attend.", + "Sorry, nothing in my closet screams 'let's do this.' It all screams 'stay home'.", + "My dog ate my homework, and then demanded a sequel.", + "I was late because time decided to move faster than me today.", + "I missed work because my bedroom was conducting a hostage situation with me as the hostage.", + "I didn't show up because I prefer to be fashionably absent.", + "My alarm clock and I had a fight, and it won by not ringing.", + "I skipped class because I felt I owed it to myself to not learn bad things.", + "I couldn't join because I was busy not panicking about it.", + "I didn't attend the meeting because I was abducted by a daydream.", + "I bailed last minute because commitment issues run in my family.", + "I arrived on time in spirit, but my body took a detour.", + "Does a fish need a bicycle? That's how much I need to do this.", + "Do pigs fly? Only then would I do that.", + "Is the sky green? No? There's my answer.", + "Would it be wise for me to agree? That's a no from the Council of Me.", + "Is this the part where I say yes? No? Good, because I won't.", + "Do I look like someone who would do that? Correct, I don't.", + "Is it Opposite Day? Because otherwise my answer is no.", + "Did I just hear myself volunteer? No, so I'll keep it that way.", + "Is this a trick question? Because I'm answering like it's one: no.", + "Am I coming? I'll answer that when water isn't wet.", + "I'm going to deliver my answer in interpretive dance. *performs a NO*", + "If I could sing, I'd sing 'I say noooo' in opera for you.", + "Imagine me dramatically yelling 'I decline!' with a cape flourish.", + "I'll write a haiku for my answer: 'No absolutely / Indubitably no / Still no, sorry'.", + "Picture a skywriter writing 'NO' above your house—that's my answer.", + "Insert an orchestra sound: dun dun dun NO.", + "I'm throwing confetti and the confetti spells 'NO'.", + "I'm holding up a scorecard that says 0, as in 0% chance.", + "Improv theater time: I play a person who says no. End scene.", + "*Cue dramatic exit with a refusal monologue*.", + "I would, but I'm Batman. (That excuse works for anything, right?)", + "I was abducted by aliens yesterday and they gave me strict instructions to avoid that.", + "Aliens invited me to a galaxy party, and I already RSVP'd yes to them.", + "I'm communicating with extraterrestrials at that time, Earth events must wait.", + "I'm on a mission to Mars in my dreams, scheduling conflict with reality.", + "I can't come; I'm the aliens' tour guide for Earth and they're very demanding.", + "The mothership is coming to pick me up any minute now.", + "I'm learning an alien language; they don't have a word for yes and neither do I right now.", + "The aliens took my enthusiasm with them when they left.", + "I suspect I'm an alien, and we historically avoid those situations.", + "My planet needs me, so I must go (meaning I must stay at home).", + "I'm running for President of the Procrastination Club; my campaign is very time-consuming.", + "My will to participate mysteriously vanished just as you asked.", + "I played the lottery of caring and lost.", + "I have an irrational fear of accidentally enjoying obligations, so I avoid them.", + "As the meme goes, ain't nobody got time for that.", + "I'm too deep into my comfort zone to even see the exit.", + "I've reached my nope limit for the day.", + "My calendar just sent me a restraining order against new events.", + "I'm in a long-term relationship with canceling plans.", + "I'm experiencing JOMO (Joy Of Missing Out) for this event.", + "Channeling Opposite Nike: 'Just don't do it.'", + "I'd rather regret not doing it than regret doing it.", + "Applying my version of Murphy's Law: if it can be avoided, it will be.", + "My answer is like Schrödinger's cat: it's both yes and no until you realize it's just no.", + "YOLO, which is why I'm saying no – I value my one life too much.", + "My horoscope app just notified me: 'Today is a good day to say no.'", + "If saying no were an Olympic sport, I'd have a gold medal.", + "I wear my refusal as a badge of honor.", + "I'm going to have to decline faster than a cheetah on energy drinks.", + "I would say yes, but then I'd have to wake up from my daydream, and reality is far less pleasant.", + "Emergency! I just ran out of chocolate, and that's all I can handle right now.", + "Fate just slid into my DMs with a big fat 'no'.", + "My brain is at full capacity with useless information, there's no room for new tasks.", + "If I help you, I'd feel proud of myself, and I can't handle that much emotion.", + "I'll give you two choices: no, or nope. You can pick.", + "Let's make it multiple choice. I choose option (D) - Not gonna happen.", + "Look on the bright side: at least I'm consistent in saying no.", + "Consider this a 'no' served with a side of sarcasm.", + "I'm stuck in a black hole of procrastination and can't escape in time to do that.", + "I only say yes during solar eclipses, and there's none scheduled.", + "My retro soul can't handle modern tasks like that.", + "The word 'yes' gives me hives; I'm avoiding an allergic reaction.", + "I had a dream I said yes; I woke up in a cold sweat. Not doing that in reality.", + "In the time it took you to ask, I already decided no.", + "I'm sorry, I can't hear you over the sound of me not doing that.", + "I think our connection is bad. If you asked me to do something, the answer is static... which means no.", + "I misheard you, I thought you said 'do you want a taco'. Since you didn't, I'm not interested.", + "My phone auto-corrected your request to 'no', weird right?", + "I got your message, but unfortunately, my spam filter caught it under 'unwanted tasks'.", + "I believe you have the wrong number; the person who says yes isn't available.", + "I speak fluent sarcasm, so my interpretation of your question is a hard no.", + "Let me put you on hold... *never takes off hold*", + "I didn't receive the memo that I was supposed to care.", + "I'm experiencing some technical difficulties in giving a darn.", + "The gods of procrastination demand I sacrifice this task by not doing it.", + "No is a complete sentence, and it's my final answer.", + "I fear agreeing could somehow trigger a zombie apocalypse, so I'm playing it safe.", + "My spidey-sense is tingling, and it's telling me not to do this.", + "The fabric of my being just isn't woven for that request.", + "They say 'follow your dreams' — and my dream is to not do that.", + "I'm just following the saying 'do what you love' — and I love saying no.", + "Not today, Satan. Actually, not any day.", + "My procrastination is doing a fantastic job; I wouldn't want to interrupt its flow.", + "No means 'New Opportunities'... for me to stay at home.", + "I made a bet that I could say no to everything for a week. I'm about to win.", + "I'm conducting a social experiment where I say no and observe what happens.", + "One of my pet peeves is doing stuff, so I simply don't.", + "Life is short, and I choose to waste it my way, not on that.", + "I've already met my quota of bad decisions for the month; saying yes would exceed it.", + "I'd love to participate, but I'm dead inside, so I just can't.", + "I have main character syndrome, and my story arc doesn't involve doing that.", + "Think of me as the final boss of saying no – undefeated.", + "Time is a non-renewable resource, and I choose not to spend any on that.", + "My free trial of doing things has expired, and I can't afford the subscription.", + "I'm on a strict 'no commitments' diet.", + "I'm doing intermittent fasting from responsibilities; today's a fasting day.", + "I only say yes on leap days, and today isn't one.", + "I can't risk unlocking my final form by being helpful.", + "I need to maintain my air of mystery by not showing up.", + "I can't let people find out I actually have free time.", + "I used all my willpower just getting out of bed this morning.", + "My life is a choose-your-own-adventure, and I flipped to the page where I say no.", + "By the power vested in me, I'm officially saying no.", + "Me, myself, and I had a meeting; we unanimously decided to decline.", + "I see your request and raise you a 'no'.", + "My enthusiasm is on backorder, and the delivery date keeps getting pushed.", + "I'm still recovering from the last time I said yes.", + "I used to do things like that, then I took an arrow to the knee.", + "It's not procrastination if I never planned on doing it.", + "I didn't choose the no life; the no life chose me.", + "Effort is currently not supported by my operating system.", + "My personal assistant (who's imaginary) already declined for me.", + "I prefer to remain an out-of-office message in human form.", + "I got lost on the way to 'yes' and ended up at 'no'.", + "If I say yes, then I'd actually have to do it—let's avoid that.", + "In the hero's journey of my life, I'm at the 'refusal of the call' stage.", + "I'm on a sabbatical from anything that sounds like work.", + "I made a New Year's resolution to say no more often. Starting now.", + "I performed a risk assessment and the results said 'no'.", + "I have a reputation to uphold—specifically, for saying no.", + "My intuition tells me this isn't the day I start being helpful.", + "The thought of doing that gave me a phantom backache. I'll pass.", + "If I had a nickel for every time I said no, I'd have a lot of nickels.", + "I'm attending a meeting of the Anti-Social Social Club (I'm the only member).", + "My brain's hard drive is full; there's no room for that task.", + "I would come, but I'm trying this new thing where I just don't.", + "I regret to inform you that I'll be vigorously not doing that.", + "I'm living vicariously through others who say yes, so I don't have to.", + "Mirror, mirror on the wall, should I do it? It said, 'Not at all.'", + "I can't say yes; it would jeopardize my standing in the Procrastinators Hall of Fame.", + "The probability of me doing that is approaching absolute zero.", + "My commitment to doing nothing would get jealous if I did something else.", + "My core competency is avoiding things, and I really need to focus on it.", + "I lack the necessary enthusiasm to even pretend to consider that.", + "I'm more of an 'observe from afar' person than a 'participate' person.", + "My brain is like a parrot that only knows one word: 'no'.", + "I'm writing a book called '101 Ways to Say No' – thanks for the material.", + "I'm a terrible liar, so I won't pretend I want to do that.", + "'No.' I'm giving that answer five stars.", + "'No' — from me, with love.", + "May the 'no' be with you.", + "Saying no is my superpower, and I'm using it to save my free time.", + "I felt burnout just hearing about it, so I'm preemptively declining.", + "I'm following the principle of least effort, which means I have to decline.", + "My life is like a sitcom about avoiding responsibility, and I can't break character.", + "I checked my availability and the results came back negative.", + "I'm going to pretend I didn't hear that request and carry on not doing it.", + "No can do—said in the nicest possible way.", + "I'm taking a mental health year (starting now), so I have to decline.", + "I decline to answer on the grounds that it may lead to doing actual work.", + "Let's be honest: we both know I'm not going to do it.", + "I'm going to have to nope out for personal reasons (personal reason: I just don't want to).", + "Not to brag, but I'm really good at not doing things.", + "Picture a Venn diagram of my schedule and your request—they don't overlap.", + "I have too many tabs open in my brain, and none of them is about that.", + "I'm booked solid with introspection (which is code for doing nothing).", + "I have a to-don't list, and this is right at the top.", + "I'm overbooked with underachieving.", + "This request is above my emotional pay grade.", + "My enthusiasm filed for bankruptcy, so I can't invest in that.", + "My calendar is full of blank spaces reserved for me-time.", + "I'm not ignoring you—I'm giving you a firm no.", + "If I take on one more thing, I'll officially become a cautionary tale.", + "Think of my refusal as a free lesson in handling rejection.", + "I admire your persistence in asking, but it's still a no.", + "I'm already living in the fast lane of doing nothing, so I can't slow down for that.", + "I can't make it; I'm extremely busy not being busy.", + "If I help you, I'll feel obligated to help others, and that's a slippery slope I'm avoiding.", + "The universe told me to 'treat myself', which I'm taking as a sign to stay home.", + "No one has ever accused me of being too helpful, and I don't plan to start now.", + "I have a fear of change, so I'm sticking with saying no.", + "My life is like a Jenga tower; one more thing might make it collapse.", + "I don't have the bandwidth (or any other width) for that.", + "Life is short, and my nap list is long.", + "Frankly, my dear, I don't give a darn (about doing it).", + "I tried seeing it from your perspective, but I still have to say no.", + "The timing isn't right—mainly because I just don't want to do it.", + "I'm in social stealth mode, so I must decline any invitations.", + "I have a chronic condition called 'idontwanna'.", + "I'm choosing couch enlightenment over worldly endeavors like that.", + "I'm off-duty indefinitely.", + "I'm in the middle of a very delicate 'doing nothing' ritual.", + "How about I promise to think about it? (I won't, but it sounds polite.)", + "I have a prior engagement: a staring contest with my wall.", + "I'll be busy exploring the great indoors.", + "The couch has accepted me as one of its own, and I can't betray its trust.", + "'No' is the new 'yes'—I'm just staying on trend.", + "I'm the hero my couch deserves, so I must stay with it.", + "The odds of me doing that are about the same as winning the lottery without buying a ticket.", + "That isn't on my bucket list; in fact, it's on my chuck-it list.", + "Please excuse me, my brain is currently out of order.", + "If there were an award for avoiding participation, I'd be the frontrunner.", + "I'm declining out of an abundance of apathy.", + "My destiny lies elsewhere—specifically, on my couch.", + "One does not simply get me to say yes.", + "It's a no from me, dawg.", + "If I say yes, I'd have to stop complaining about being busy, and I can't have that.", + "My energy is solar-powered, and it's nighttime.", + "I'll take one for the team by not joining this effort.", + "I put the 'pro' in procrastinate, and I won't ruin my streak.", + "Even my shadow decided to stay home, so I will too.", + "Luckily, I have procrastination insurance. It covers me for situations like this.", + "I'm not my circus, and those are not my monkeys—so it's not my act to perform.", + "I don't have enough mana for that quest.", + "The spirit is willing, but the flesh has already ordered takeout and settled in.", + "My will to do that is in the shop for repairs.", + "I could list 1000 reasons why I'm saying no, but I'll spare you.", + "I decline now, to avoid declining later.", + "I'm choosing myself over this commitment—so, no.", + "I'll spare you a half-hearted yes and give you an honest no.", + "If my life were a movie, this is the part I say no and roll credits.", + "I'm closing the door on that idea (literally and figuratively).", + "My intuition screamed 'no' so loudly, I had to answer accordingly.", + "'No' is my happy place.", + "The answer you seek is not available at this time (try again never).", + "To quote a famous response: 'No.'", + "I'm exercising my right to say no.", + "Think of this as a strategic veto.", + "I've never been so sure of anything as I am of saying no.", + "I woke up today and decided it's a 'no' day.", + "Today, I only speak in negatives.", + "I'm on a mission to maintain zero commitments.", + "Even my FitBit is telling me to stay put.", + "I'm choosing Netflix over stress, sorry.", + "No, in the most heartfelt, humorous way possible.", + "My calendar is on strike.", + "I choose me, so I'm saying no to you.", + "I'm doing everyone a favor by sticking to what I'm good at: saying no.", + "No is part of my self-care routine.", + "This is a guilt-free no.", + "If I helped, I'd be robbing you of the chance to do it yourself.", + "My schedule is in airplane mode.", + "I'm not ghosting you; I'm giving you a firm no.", + "I'm at peace with saying no.", + "Negative. Full stop.", + "I must protect my sanity at all costs—so no.", + "I didn't come this far to only come this far. In fact, I'm not coming at all.", + "It's a 'no' from the committee of me, myself, and I.", + "I must decline; I've reached my threshold for giving a darn.", + "Even if I wanted to, I couldn't. And I don't want to.", + "I'm going to seize the opportunity to do nothing instead.", + "My will to participate has left the building.", + "Your request has been denied due to a lack of interest on my part.", + "Let's just assume I said no and move on.", + "I'm currently out of order. Please try again never.", + "Alas, I must decline.", + "Declining this request sparks joy in me.", + "I could maybe be convinced, but I'd likely still say no in the end.", + "I have a philosophical objection to doing that.", + "If I were any more indifferent, I'd be a statue.", + "I need to prioritize my priorities, which currently are sleeping and snacking.", + "My schedule has a built-in 'nope' feature.", + "Saying yes would break the laws of physics in my personal universe.", + "At this point, saying yes would just feel wrong.", + "I promised myself I'd stick to being unhelpful this week.", + "My laziness is in full bloom, and I cannot uproot it for this.", + "I'm allergic to obligations and break out in excuses.", + "I'll sit this one out and let karma reward my honesty.", + "No can do—said in the nicest way possible (still no).", + "Think of my 'no' as an act of self-care.", + "I'm exercising my freedom of choice, and I choose no.", + "I'm hard at work doing nothing at all.", + "I would help, but that would contradict my life's mission statement.", + "No, but I appreciate your optimism.", + "I have a black belt in avoidance, and I'd hate to waste it (so, no).", + "I'm in the No Witness Protection Program (I say no and then hide).", + "I have to stay home and make sure gravity continues to work. (Someone has to.)", + "If I go, who will hold down the fort? (The fort being my couch).", + "I'm in the middle of a life audit, and unnecessary commitments didn't make the cut.", + "Declining this is actually part of my five-year plan.", + "Picture a Venn diagram of my schedule and your request—they don't overlap.", + "I have too many tabs open in my brain, and none of them is about that task.", + "I'm booked solid with introspection (which is code for doing nothing).", + "I have a to-don't list, and this is right at the top.", + "I'm overbooked with underachieving.", + "This request is above my emotional pay grade.", + "My enthusiasm filed for bankruptcy, so I can't invest in that.", + "My calendar is full of blank spaces reserved for me-time.", + "I'm not ignoring you—I'm giving you a very clear 'no'.", + "If I take on one more thing, I'll officially become a cautionary tale.", + "Think of my refusal as a free lesson in handling rejection.", + "I admire your persistence in asking, but it's still a no.", + "I'm already living in the fast lane of doing nothing, so I can't slow down for that.", + "I can't make it; I'm extremely busy not being busy.", + "If I help you, I'll feel obligated to help others, and that's a slippery slope I'm avoiding.", + "The universe told me to 'treat myself', which I'm taking as a sign to stay home.", + "No one has ever accused me of being too helpful, and I don't plan to start now.", + "I have a fear of change, so I'm sticking with saying no.", + "My life is like a Jenga tower; one more thing might make it collapse.", + "I don't have the bandwidth (or any other width) for that.", + "Life is short, and my nap list is long.", + "Frankly, my dear, I don't give a darn (about doing it).", + "I tried seeing it from your perspective, but I still have to say no.", + "The timing isn't right—mainly because I just don't want to do it.", + "I'm in social stealth mode, so I must decline any invitations.", + "I have a chronic condition called 'idontwanna'.", + "I'm off-duty indefinitely.", + "I'm in the middle of a very delicate 'doing nothing' ritual.", + "How about I promise to think about it? (I won't, but it sounds polite.)", + "I have a prior engagement: a staring contest with my wall.", + "I'll be busy exploring the great indoors.", + "The couch has accepted me as one of its own, and I can't betray its trust.", + "'No' is the new 'yes'—I'm just staying on trend.", + "I'm the hero my couch deserves, so I must stay with it.", + "The odds of me doing that are about the same as winning the lottery without buying a ticket.", + "That isn't on my bucket list; in fact, it's on my chuck-it list.", + "Please excuse me, my brain is currently out of order.", + "If there were an award for avoiding participation, I'd be the frontrunner.", + "I'm declining out of an abundance of apathy.", + "My destiny lies elsewhere—specifically, on my couch.", + "One does not simply get me to say yes.", + "It's a no from me, dawg.", + "If I say yes, I'd have to stop complaining about being busy, and I can't have that.", + "My energy is solar-powered, and it's nighttime.", + "I'll take one for the team by not joining this effort.", + "I put the 'pro' in procrastinate, and I won't ruin my streak.", + "Even my shadow decided to stay home, so I will too.", + "Luckily, I have procrastination insurance. It covers me for situations like this.", + "Not my circus, not my monkeys—so definitely not my act to perform.", + "I don't have enough mana for that quest.", + "The spirit is willing, but the flesh has already ordered takeout and settled in.", + "My will to do that is in the shop for repairs.", + "I could list 1000 reasons why I'm saying no, but I'll spare you.", + "I decline now, to avoid declining later.", + "I'm choosing myself over this commitment—so, no.", + "I'll spare you a half-hearted yes and give you an honest no.", + "If my life were a movie, this is the part I say no and roll credits.", + "I'm closing the door on that idea (literally and figuratively).", + "My intuition screamed 'no' so loudly, I had to answer accordingly.", + "'No' is my happy place.", + "The answer you seek is not available at this time (try again never).", + "To quote a famous response: 'No.'", + "I'm exercising my right to say no.", + "Think of this as a strategic veto.", + "I've never been so sure of anything as I am of saying no.", + "I woke up today and thought, 'let's not.'", + "Today, I only speak in negatives.", + "I'm on a mission to maintain zero commitments.", + "Even my FitBit is telling me to stay put.", + "I'm choosing Netflix over stress, sorry.", + "No, in the most heartfelt, humorous way possible.", + "My calendar is on strike.", + "I'm doing everyone a favor by sticking to what I'm good at: saying no.", + "No is part of my self-care routine.", + "This is a guilt-free no.", + "If I helped, I'd be robbing you of the chance to do it yourself.", + "My schedule is in airplane mode.", + "I'm not ghosting you; I'm giving you a firm no.", + "I'm at peace with saying no.", + "Negative. Full stop.", + "I must protect my sanity at all costs—so no.", + "Alas, I must decline.", + "Declining this request sparks joy in me.", + "I'm not ignoring you—I'm giving you a very clear 'no'.", + "Pretend I gave you a clever excuse and just take the 'no' as implied." + ]; +} diff --git a/api/Utils/ParserUtils.cs b/api/Utils/ParserUtils.cs new file mode 100644 index 0000000..c3df054 --- /dev/null +++ b/api/Utils/ParserUtils.cs @@ -0,0 +1,48 @@ +using System.Text.RegularExpressions; + +namespace BonchCalendar.Utils; + +public static partial class ParserUtils +{ + public static (TimeSpan startTime, TimeSpan endTime) GetTimesFromLabel(string label) + { + (string startTime, string endTime) = label switch + { + "1" => ("9:00", "10:35"), + "2" => ("10:45", "12:20"), + "3" => ("13:00", "14:35"), + "4" => ("14:45", "16:20"), + "5" => ("16:30", "18:05"), + "6" => ("18:15", "19:50"), + "7" => ("20:00", "21:35"), + "Ф1" => ("9:00", "10:30"), + "Ф2" => ("10:30", "12:00"), + "Ф3" => ("12:00", "13:30"), + "Ф4" => ("13:30", "15:00"), + "Ф5" => ("15:00", "16:30"), + "Ф6" => ("16:30", "18:00"), + "Ф7" => ("18:00", "19:30"), + _ => throw new NotImplementedException(), + }; + + return (TimeSpan.Parse(startTime), TimeSpan.Parse(endTime)); + } + + [GeneratedRegex(@"^(?\S+)\s\((?\d+:\d+)-(?\d+:\d+)\)$")] + public static partial Regex TimeLabelRegex(); + + [GeneratedRegex(@"\d+")] + public static partial Regex NumberRegex(); + + [GeneratedRegex(@"^(?\d+),\sБ22\/(?\d)$")] + public static partial Regex AuditoriumRegex(); + + [GeneratedRegex(@"^(?\d+),\sпр\.Большевиков,22,к\.(?\d)$")] + public static partial Regex AuditoriumAltRegex(); + + [GeneratedRegex(@"^(?\d)\s\((?\d+\.\d+)-(?\d+\.\d+)\)$")] + public static partial Regex ExamTimeRegex(); + + [GeneratedRegex(@"^(?\d+:\d+)-(?\d+:\d+)$")] + public static partial Regex ExamTimeAltRegex(); +} diff --git a/api/appsettings.Development.json b/api/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/api/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/api/appsettings.json b/api/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/api/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/app/.dockerignore b/app/.dockerignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/app/.dockerignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/app/.env b/app/.env new file mode 100644 index 0000000..793f808 --- /dev/null +++ b/app/.env @@ -0,0 +1 @@ +VITE_BACKEND_HOST=https://api.bonch.xfox111.net diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/app/Dockerfile b/app/Dockerfile new file mode 100644 index 0000000..43e153c --- /dev/null +++ b/app/Dockerfile @@ -0,0 +1,30 @@ +# Use the official Node.js image as the base image +FROM node:latest AS builder + +ARG API_HOST=https://api.bonch.xfox111.net + +# Set the working directory inside the container +WORKDIR /app + +# Copy the package.json and package-lock.json files to the working directory +COPY package*.json ./ + +# Install the app dependencies +RUN npm install + +# Copy the app source code to the working directory +COPY . . + +RUN echo "VITE_BACKEND_HOST=${API_HOST}" > .env.production + +# Build the app +RUN npm run build + +# Use the official Nginx image as the base image +FROM nginx:latest AS prod + +# Copy the build output to replace the default Nginx contents +COPY --from=builder /app/dist /usr/share/nginx/html + +# Expose port 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/app/eslint.config.js b/app/eslint.config.js new file mode 100644 index 0000000..65a8f9d --- /dev/null +++ b/app/eslint.config.js @@ -0,0 +1,27 @@ +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; +import { defineConfig, globalIgnores } from "eslint/config"; + +export default defineConfig([ + globalIgnores(["dist"]), + { + files: ["**/*.{ts,tsx}"], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + rules: + { + "@typescript-eslint/no-unused-vars": "warn" + } + }, +]); diff --git a/app/index.html b/app/index.html new file mode 100644 index 0000000..65c3aaf --- /dev/null +++ b/app/index.html @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + Бонч.Календарь + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + diff --git a/app/package-lock.json b/app/package-lock.json new file mode 100644 index 0000000..f848d95 --- /dev/null +++ b/app/package-lock.json @@ -0,0 +1,5255 @@ +{ + "name": "bonch-calendar", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "bonch-calendar", + "version": "0.0.0", + "dependencies": { + "@fluentui/react-components": "^9.72.7", + "@fluentui/react-icons": "^2.0.314", + "@fluentui/react-motion-components-preview": "^0.14.1", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-localization": "^2.0.6" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.0", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@vitejs/plugin-react": "^5.1.0", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.46.3", + "vite": "^7.2.2" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "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/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "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/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/devtools": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@floating-ui/devtools/-/devtools-0.2.3.tgz", + "integrity": "sha512-ZTcxTvgo9CRlP7vJV62yCxdqmahHTGpSTi5QaTDgGoyQq0OyjaVZhUhXv/qdkQFOI3Sxlfmz0XGG4HaZMsDf8Q==", + "license": "MIT", + "peerDependencies": { + "@floating-ui/dom": "^1.0.0" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@fluentui/keyboard-keys": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@fluentui/keyboard-keys/-/keyboard-keys-9.0.8.tgz", + "integrity": "sha512-iUSJUUHAyTosnXK8O2Ilbfxma+ZyZPMua5vB028Ys96z80v+LFwntoehlFsdH3rMuPsA8GaC1RE7LMezwPBPdw==", + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.1" + } + }, + "node_modules/@fluentui/priority-overflow": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@fluentui/priority-overflow/-/priority-overflow-9.2.1.tgz", + "integrity": "sha512-WH5dv54aEqWo/kKQuADAwjv66W6OUMFllQMjpdkrktQp7pu4JXtmF60iYcp9+iuIX9iCeW01j8gNTU08MQlfIQ==", + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.1" + } + }, + "node_modules/@fluentui/react-accordion": { + "version": "9.8.14", + "resolved": "https://registry.npmjs.org/@fluentui/react-accordion/-/react-accordion-9.8.14.tgz", + "integrity": "sha512-jTcfYDRUotRhUEjE1LeG1Qm10515CQUKxHWQhppBYhq7yAZcS5jOms5tMZHtHs0EQsWv3nMgUYYqoOqAsU0jDQ==", + "license": "MIT", + "dependencies": { + "@fluentui/react-aria": "^9.17.6", + "@fluentui/react-context-selector": "^9.2.12", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-motion": "^9.11.4", + "@fluentui/react-motion-components-preview": "^0.14.1", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-alert": { + "version": "9.0.0-beta.129", + "resolved": "https://registry.npmjs.org/@fluentui/react-alert/-/react-alert-9.0.0-beta.129.tgz", + "integrity": "sha512-afS5Mvf9EH5je3ZOnF96GNaXL5nA/eI69AhO4nsbsvc1RaO/CkEt9+6iVyGy2zeqbQgpsP9UkNwEYyToQ1CrzA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-avatar": "^9.9.12", + "@fluentui/react-button": "^9.6.12", + "@fluentui/react-icons": "^2.0.239", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-aria": { + "version": "9.17.6", + "resolved": "https://registry.npmjs.org/@fluentui/react-aria/-/react-aria-9.17.6.tgz", + "integrity": "sha512-O421keKMgf9BkHH15kTnKGFuCFKN3ukydJLEfSQJmOfdAHyJMzAul8/zMvkd4vmMr84+PtZUD1+Tylk4NvpN4g==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.8", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-utilities": "^9.25.4", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-avatar": { + "version": "9.9.12", + "resolved": "https://registry.npmjs.org/@fluentui/react-avatar/-/react-avatar-9.9.12.tgz", + "integrity": "sha512-dlJ5mOKCDChMAECFhpcPHoQicA28ATWQXLtz26hAuVJH2/gC/6mZ0j7drIVl9YECqT/ZbZ3/hpVeZu/S/FVrOA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-badge": "^9.4.11", + "@fluentui/react-context-selector": "^9.2.12", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-popover": "^9.12.12", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-tooltip": "^9.8.11", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-badge": { + "version": "9.4.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-badge/-/react-badge-9.4.11.tgz", + "integrity": "sha512-u2gTg+QeD5uaieAwE89n8MLg2MyZN/kGMx3hJewFKtq3SzvU4xcgcna2Gp4UgpaA3pnGZsJjjjDIHwsv4EyO9Q==", + "license": "MIT", + "dependencies": { + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-breadcrumb": { + "version": "9.3.12", + "resolved": "https://registry.npmjs.org/@fluentui/react-breadcrumb/-/react-breadcrumb-9.3.12.tgz", + "integrity": "sha512-cT5xmYQbAYH7HslJu6O5WvSYzsBvaQ54Q6yIPgV5kCo5n3M6OSrJ0Ga6Zbfqid/GnY4G60FfjOvbfHNNhmx2Sw==", + "license": "MIT", + "dependencies": { + "@fluentui/react-aria": "^9.17.6", + "@fluentui/react-button": "^9.6.12", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-link": "^9.7.0", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-button": { + "version": "9.6.12", + "resolved": "https://registry.npmjs.org/@fluentui/react-button/-/react-button-9.6.12.tgz", + "integrity": "sha512-seI9L9O0fCHzlfKD/via1qqzaLFeiFKQeR1/97nXL06reC3DqLSCeiZP3UTxFljFE1CYZQRJfk1wH/D6j0ZCTA==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.8", + "@fluentui/react-aria": "^9.17.6", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-card": { + "version": "9.5.6", + "resolved": "https://registry.npmjs.org/@fluentui/react-card/-/react-card-9.5.6.tgz", + "integrity": "sha512-hCY6VWrKqq+y0yqUkqgkpTN5TVJSU5ZlKtZU+Sed+TlnKlojkS6cYRvsnWdAKwyFLJF9ZYTn+uos9Vi0wQyjtg==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.8", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-text": "^9.6.11", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-carousel": { + "version": "9.8.12", + "resolved": "https://registry.npmjs.org/@fluentui/react-carousel/-/react-carousel-9.8.12.tgz", + "integrity": "sha512-Gjn6cd67FodcjfU2MQTBI2xjijzgy54TdQA8vxObZ27I6y9OHeDR07PWTqaCkX8mcBR8ilTxVD5bQ+zuqfb66g==", + "license": "MIT", + "dependencies": { + "@fluentui/react-aria": "^9.17.6", + "@fluentui/react-button": "^9.6.12", + "@fluentui/react-context-selector": "^9.2.12", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-tooltip": "^9.8.11", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1", + "embla-carousel": "^8.5.1", + "embla-carousel-autoplay": "^8.5.1", + "embla-carousel-fade": "^8.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-checkbox": { + "version": "9.5.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-checkbox/-/react-checkbox-9.5.11.tgz", + "integrity": "sha512-M8DTBQK0Z7+HKfRx4mjypH0fEagKK7YMNhGMy18aW3iYWeooA0ut81MzsRM5feqhl+Q8v4VJ0aN9qHNqshkD5g==", + "license": "MIT", + "dependencies": { + "@fluentui/react-field": "^9.4.11", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-label": "^9.3.11", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-color-picker": { + "version": "9.2.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-color-picker/-/react-color-picker-9.2.11.tgz", + "integrity": "sha512-L1ZKJAyioey3glmzMrpawUrzsdu/Nz0m6nVMOznJVuw0vu0BfQuMh/1/0QOoGYXFEbsc4+gSGSCnah4X0EJIsQ==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.3.4", + "@fluentui/react-context-selector": "^9.2.12", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-combobox": { + "version": "9.16.12", + "resolved": "https://registry.npmjs.org/@fluentui/react-combobox/-/react-combobox-9.16.12.tgz", + "integrity": "sha512-SimZpXzTGyDAGHQZmzUl9AsrIOlLDinTbvEwELEYh9X+yE33SZatcPwdpCmBXldBOs/eh+xOuNSOwgerJ3T3qQ==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.8", + "@fluentui/react-aria": "^9.17.6", + "@fluentui/react-context-selector": "^9.2.12", + "@fluentui/react-field": "^9.4.11", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-portal": "^9.8.8", + "@fluentui/react-positioning": "^9.20.10", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-components": { + "version": "9.72.7", + "resolved": "https://registry.npmjs.org/@fluentui/react-components/-/react-components-9.72.7.tgz", + "integrity": "sha512-tuC8ZMBQicF4p+f9MJv9cVYZUSktQVreAGJq/YJxQ0Ts1mO2rnAuIBkBFlgjnjyebDiAO1FoAAz/wW99hrIh6A==", + "license": "MIT", + "dependencies": { + "@fluentui/react-accordion": "^9.8.14", + "@fluentui/react-alert": "9.0.0-beta.129", + "@fluentui/react-aria": "^9.17.6", + "@fluentui/react-avatar": "^9.9.12", + "@fluentui/react-badge": "^9.4.11", + "@fluentui/react-breadcrumb": "^9.3.12", + "@fluentui/react-button": "^9.6.12", + "@fluentui/react-card": "^9.5.6", + "@fluentui/react-carousel": "^9.8.12", + "@fluentui/react-checkbox": "^9.5.11", + "@fluentui/react-color-picker": "^9.2.11", + "@fluentui/react-combobox": "^9.16.12", + "@fluentui/react-dialog": "^9.16.3", + "@fluentui/react-divider": "^9.4.11", + "@fluentui/react-drawer": "^9.10.9", + "@fluentui/react-field": "^9.4.11", + "@fluentui/react-image": "^9.3.11", + "@fluentui/react-infobutton": "9.0.0-beta.107", + "@fluentui/react-infolabel": "^9.4.12", + "@fluentui/react-input": "^9.7.11", + "@fluentui/react-label": "^9.3.11", + "@fluentui/react-link": "^9.7.0", + "@fluentui/react-list": "^9.6.6", + "@fluentui/react-menu": "^9.20.5", + "@fluentui/react-message-bar": "^9.6.14", + "@fluentui/react-motion": "^9.11.4", + "@fluentui/react-nav": "^9.3.14", + "@fluentui/react-overflow": "^9.6.5", + "@fluentui/react-persona": "^9.5.12", + "@fluentui/react-popover": "^9.12.12", + "@fluentui/react-portal": "^9.8.8", + "@fluentui/react-positioning": "^9.20.10", + "@fluentui/react-progress": "^9.4.11", + "@fluentui/react-provider": "^9.22.11", + "@fluentui/react-radio": "^9.5.11", + "@fluentui/react-rating": "^9.3.11", + "@fluentui/react-search": "^9.3.11", + "@fluentui/react-select": "^9.4.11", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-skeleton": "^9.4.11", + "@fluentui/react-slider": "^9.5.11", + "@fluentui/react-spinbutton": "^9.5.11", + "@fluentui/react-spinner": "^9.7.11", + "@fluentui/react-swatch-picker": "^9.4.11", + "@fluentui/react-switch": "^9.4.11", + "@fluentui/react-table": "^9.19.5", + "@fluentui/react-tabs": "^9.10.7", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-tag-picker": "^9.7.12", + "@fluentui/react-tags": "^9.7.12", + "@fluentui/react-teaching-popover": "^9.6.12", + "@fluentui/react-text": "^9.6.11", + "@fluentui/react-textarea": "^9.6.11", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-toast": "^9.7.9", + "@fluentui/react-toolbar": "^9.6.12", + "@fluentui/react-tooltip": "^9.8.11", + "@fluentui/react-tree": "^9.15.6", + "@fluentui/react-utilities": "^9.25.4", + "@fluentui/react-virtualizer": "9.0.0-alpha.107", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-context-selector": { + "version": "9.2.12", + "resolved": "https://registry.npmjs.org/@fluentui/react-context-selector/-/react-context-selector-9.2.12.tgz", + "integrity": "sha512-4hj+rv+4Uwn9EeDyXD1YCEpVkm0iMLG403QAGd5vZZhcgB2tg/iazewKeTff+HMRkusx+lWBYzBEGcRohY/FiA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-utilities": "^9.25.4", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0", + "scheduler": ">=0.19.0" + } + }, + "node_modules/@fluentui/react-dialog": { + "version": "9.16.3", + "resolved": "https://registry.npmjs.org/@fluentui/react-dialog/-/react-dialog-9.16.3.tgz", + "integrity": "sha512-aUnErTbSf2oqrqbQOCrjXp/12qHVfnxCR71/5hXJLME7BtYZ/m2lvs5r9MTjQSXBy8ar4G5jobS/+XJ0Lq3XqA==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.8", + "@fluentui/react-aria": "^9.17.6", + "@fluentui/react-context-selector": "^9.2.12", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-motion": "^9.11.4", + "@fluentui/react-motion-components-preview": "^0.14.1", + "@fluentui/react-portal": "^9.8.8", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-divider": { + "version": "9.4.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-divider/-/react-divider-9.4.11.tgz", + "integrity": "sha512-aESagOX6l7Ja9lb+3zJa6V5m1mjFnI4NEu8TccAu1VUlMZxX6flbMBJplgjN76dJjcHgs8uoa5xxxD74WNZBXg==", + "license": "MIT", + "dependencies": { + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-drawer": { + "version": "9.10.9", + "resolved": "https://registry.npmjs.org/@fluentui/react-drawer/-/react-drawer-9.10.9.tgz", + "integrity": "sha512-tlHZBkILOHnA7Lg2v/vzmOvTNrPYJnPJAqiceuFlUZWncIWWAUfpw4Teh5V0wGNr6/yC/HjUD5xnynvIhr/ZuA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-dialog": "^9.16.3", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-motion": "^9.11.4", + "@fluentui/react-motion-components-preview": "^0.14.1", + "@fluentui/react-portal": "^9.8.8", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-field": { + "version": "9.4.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-field/-/react-field-9.4.11.tgz", + "integrity": "sha512-kF93G+LGEKaFJcEAUHJKZUc1xeV/q+JTygYVnEDkPbQ/4j+l+J3rVuHL8U7bhE+8cJG3wDP8jt4jqHsDgKyn5w==", + "license": "MIT", + "dependencies": { + "@fluentui/react-context-selector": "^9.2.12", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-label": "^9.3.11", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-icons": { + "version": "2.0.314", + "resolved": "https://registry.npmjs.org/@fluentui/react-icons/-/react-icons-2.0.314.tgz", + "integrity": "sha512-oIfhvtrcQatubmJeUTQ6ME/ghlU0X8FY5eA8YwhgqtrvnPst5FOAIAfsQfFA30U7uPgcxg9psWYfQbtAc9DZhA==", + "license": "MIT", + "dependencies": { + "@griffel/react": "^1.0.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "react": ">=16.8.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-image": { + "version": "9.3.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-image/-/react-image-9.3.11.tgz", + "integrity": "sha512-aLpz0/C6T0Uit6SmyhOJjYBvndZzfvmKv1vg+JRnE0aHS5jSUPoCLI6apxyMC6/LcqqTBklpqK3AD9kYpUxfqQ==", + "license": "MIT", + "dependencies": { + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-infobutton": { + "version": "9.0.0-beta.107", + "resolved": "https://registry.npmjs.org/@fluentui/react-infobutton/-/react-infobutton-9.0.0-beta.107.tgz", + "integrity": "sha512-BcI4e+Oj1B/Qk4CMd0O9H0YF+IL4nhK8xuzI5bsZ5mdCaXiwIBgy5RyP8HVSq3y+Ml4XD2IRwufplcxF2cgTOA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-icons": "^2.0.237", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-label": "^9.3.11", + "@fluentui/react-popover": "^9.12.12", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-infolabel": { + "version": "9.4.12", + "resolved": "https://registry.npmjs.org/@fluentui/react-infolabel/-/react-infolabel-9.4.12.tgz", + "integrity": "sha512-inXlz5EAwQHKsGyB3wc5WmgQ1F9zc18x0HRd/otc2R7Oo1yRW5hXQCG5K5A9/wUge2pRiQVcBCsIurggmCNUhA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-label": "^9.3.11", + "@fluentui/react-popover": "^9.12.12", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.8.0 <20.0.0", + "@types/react-dom": ">=16.8.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.8.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-input": { + "version": "9.7.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-input/-/react-input-9.7.11.tgz", + "integrity": "sha512-ae/5ttJf25+J8akeEXpXRFqUAePPt2Moyfx4Tj0u7ZgG1U9IFbcBsshKEHAmIaygueXf6KdRyOduh1CF6a/D2w==", + "license": "MIT", + "dependencies": { + "@fluentui/react-field": "^9.4.11", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-jsx-runtime": { + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/@fluentui/react-jsx-runtime/-/react-jsx-runtime-9.3.3.tgz", + "integrity": "sha512-KOy85JqR6MSmp7OKUk/IPleaRlUSWF247AM2Ksu9uEKzDBQ2MO3sYUt8X9457GZjIuKLn5J2Vk127W/Khe+/Bg==", + "license": "MIT", + "dependencies": { + "@fluentui/react-utilities": "^9.25.4", + "@swc/helpers": "^0.5.1", + "react-is": "^17.0.2" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "react": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-label": { + "version": "9.3.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-label/-/react-label-9.3.11.tgz", + "integrity": "sha512-9LORj4JQJCbp2J5ftW7ZjDxzD3Y4BkszX3Y7L1mK8DPRVAKOuGiakbH7U0q7ClGOMhCinWIQJjKAwzPZLo7xgA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-link": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@fluentui/react-link/-/react-link-9.7.0.tgz", + "integrity": "sha512-NQ5Jhe5WBYfANSmIcl6fE/oBeh7G4iAq1FU9L/hyva5dxQ9OtiOpU5wxqVFLKEID/r144rhdtOZPL5AcAuJKdg==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.8", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-list": { + "version": "9.6.6", + "resolved": "https://registry.npmjs.org/@fluentui/react-list/-/react-list-9.6.6.tgz", + "integrity": "sha512-t0ret56WXP86rDfnhuRrWg/DuS2zZkomB/Nu444rVygE8hsjPUTm5DXx7JKy+sGKVLyFbtsbXNMICkbxhGSSRA==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.8", + "@fluentui/react-checkbox": "^9.5.11", + "@fluentui/react-context-selector": "^9.2.12", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.8.0 <20.0.0", + "@types/react-dom": ">=16.8.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.8.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-menu": { + "version": "9.20.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-menu/-/react-menu-9.20.5.tgz", + "integrity": "sha512-vshb/OXBZxvk+ghdmdVb2mJ/LJBYjlwpZRhWGJ8ZU0hmPTh74m5jTFWditSk8aL9oMvVuIo0MYLQyUJyJsFoqg==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.8", + "@fluentui/react-aria": "^9.17.6", + "@fluentui/react-context-selector": "^9.2.12", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-portal": "^9.8.8", + "@fluentui/react-positioning": "^9.20.10", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-message-bar": { + "version": "9.6.14", + "resolved": "https://registry.npmjs.org/@fluentui/react-message-bar/-/react-message-bar-9.6.14.tgz", + "integrity": "sha512-UR4Uvkx4VHQyS04T5ikf9gYOH52dloo1vjmK+pFKiqRzZhflHEXID9R1AZFuuZ572KUMXnxRlyEevpXnWqE70w==", + "license": "MIT", + "dependencies": { + "@fluentui/react-button": "^9.6.12", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-link": "^9.7.0", + "@fluentui/react-motion": "^9.11.4", + "@fluentui/react-motion-components-preview": "^0.14.1", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.8.0 <20.0.0", + "@types/react-dom": ">=16.8.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.8.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-motion": { + "version": "9.11.4", + "resolved": "https://registry.npmjs.org/@fluentui/react-motion/-/react-motion-9.11.4.tgz", + "integrity": "sha512-rLxz6DSAtp3O+W+mJnov2qXtvZkIgcC1BQOAyUH6tl6u2YmsC1/zRKWhVsf/WUgZwqu3G4jlq15ptyuCITAcDA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-utilities": "^9.25.4", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.8.0 <20.0.0", + "@types/react-dom": ">=16.8.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.8.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-motion-components-preview": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@fluentui/react-motion-components-preview/-/react-motion-components-preview-0.14.1.tgz", + "integrity": "sha512-+2MK7d2g3mD+6Z3o9/fitO+V4u5OKGeRUoUjwlU1v56JHP43hj+NCJynoe4Cym8FeSwTyipks6hvdqBF4W+jtw==", + "license": "MIT", + "dependencies": { + "@fluentui/react-motion": "*", + "@fluentui/react-utilities": "*", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-nav": { + "version": "9.3.14", + "resolved": "https://registry.npmjs.org/@fluentui/react-nav/-/react-nav-9.3.14.tgz", + "integrity": "sha512-0Lylul5g/9y3Cay5qHLtzW4SB9kdkTmvjHSffPJZDKE/Wv7GBbDypBxoB+f2L1K4f0qzRJ1NvIiwatH4hAsUDA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-aria": "^9.17.6", + "@fluentui/react-button": "^9.6.12", + "@fluentui/react-context-selector": "^9.2.12", + "@fluentui/react-divider": "^9.4.11", + "@fluentui/react-drawer": "^9.10.9", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-motion": "^9.11.4", + "@fluentui/react-motion-components-preview": "^0.14.1", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-tooltip": "^9.8.11", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-overflow": { + "version": "9.6.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-overflow/-/react-overflow-9.6.5.tgz", + "integrity": "sha512-4MlXASDodkwk4QWhUPLgMbUPwDYAOAWDnQGJz4q6Hs9eZvx83dSpWdWjkmQ6mwjYf2HwooMkqsjR/kAFvg+ipg==", + "license": "MIT", + "dependencies": { + "@fluentui/priority-overflow": "^9.2.1", + "@fluentui/react-context-selector": "^9.2.12", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-persona": { + "version": "9.5.12", + "resolved": "https://registry.npmjs.org/@fluentui/react-persona/-/react-persona-9.5.12.tgz", + "integrity": "sha512-ja3t1o6XDJWCJnOVDTM48G7bFPAbNxcsQKwAPfiuROVu8ODbTQefutCHl0Hno40AsftQk6N4zGbKcn7BYSZ09Q==", + "license": "MIT", + "dependencies": { + "@fluentui/react-avatar": "^9.9.12", + "@fluentui/react-badge": "^9.4.11", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-popover": { + "version": "9.12.12", + "resolved": "https://registry.npmjs.org/@fluentui/react-popover/-/react-popover-9.12.12.tgz", + "integrity": "sha512-IytuasB4b4lCnEhFC0OC66a3mzBSePLpg78/BceKYepuG7IC6iGuCwYartqSQCSUlSU12rT02/V0rqCO81f4Nw==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.8", + "@fluentui/react-aria": "^9.17.6", + "@fluentui/react-context-selector": "^9.2.12", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-portal": "^9.8.8", + "@fluentui/react-positioning": "^9.20.10", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-portal": { + "version": "9.8.8", + "resolved": "https://registry.npmjs.org/@fluentui/react-portal/-/react-portal-9.8.8.tgz", + "integrity": "sha512-RVvhWYfcwIUYXiokgFw3oxb7Q6xox2e7jcsgFtheDm2X/BHT6WJigW4OaCjOkvugkBEYQkwgIpL9iS2QG3HMFA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-positioning": { + "version": "9.20.10", + "resolved": "https://registry.npmjs.org/@fluentui/react-positioning/-/react-positioning-9.20.10.tgz", + "integrity": "sha512-mjuiqh+urV5SzAP2NfzUzsvtWut0aNcO9m/jIuz374iTVGRfDNeVIl7aPI4yK5sdCDR6dGALiNMTFHpjz1YXyw==", + "license": "MIT", + "dependencies": { + "@floating-ui/devtools": "^0.2.3", + "@floating-ui/dom": "^1.6.12", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-progress": { + "version": "9.4.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-progress/-/react-progress-9.4.11.tgz", + "integrity": "sha512-L0Yh2D0vLPJX0jYfc9VHf8c/idW+e/oRxYNXfrTrvtW1bX80bAmrXWgdRPr/VEtvbJh//2ol2TRmTTQsn2ECNQ==", + "license": "MIT", + "dependencies": { + "@fluentui/react-field": "^9.4.11", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-provider": { + "version": "9.22.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-provider/-/react-provider-9.22.11.tgz", + "integrity": "sha512-XrinA7DVEqsPHeN9XljwTENiIQypiF9cmDYXHN9Emsz6Od4hnmsbt4pnR4Xsf+GcSxVtxkIImfgwtS0aENzYbQ==", + "license": "MIT", + "dependencies": { + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/core": "^1.16.0", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-radio": { + "version": "9.5.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-radio/-/react-radio-9.5.11.tgz", + "integrity": "sha512-tMxCcqRSSYqYr6hy1dKkzS6LymRc8wM089vr4eBLPQCGCvi3OCd6P7XH8aIcXnzxE3+v03Gs7E/wbzi2CXN6gA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-field": "^9.4.11", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-label": "^9.3.11", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-rating": { + "version": "9.3.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-rating/-/react-rating-9.3.11.tgz", + "integrity": "sha512-9Bl/sESNbFTbz8peGt9vxLxHDO0AWvS12oMiQ80S1GQOt1ua4S9/SKC83OvyVLEdpBDpBkXTelNz5whczcWexQ==", + "license": "MIT", + "dependencies": { + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.8.0 <20.0.0", + "@types/react-dom": ">=16.8.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.8.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-search": { + "version": "9.3.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-search/-/react-search-9.3.11.tgz", + "integrity": "sha512-inLoPgbGnupfwhBxFS59mF/ThsntusfYp9TaaTB3SJmqfEEx6YXi5soxszzrXsNvrqpgEoCGIduRpEICuUz5pw==", + "license": "MIT", + "dependencies": { + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-input": "^9.7.11", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-select": { + "version": "9.4.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-select/-/react-select-9.4.11.tgz", + "integrity": "sha512-/mcdl/lkKccT+GKXu22y2/ANeLhFNUdjkOX+0rvBdl3u49xkqS9Y4Bi0zM1EhhTV2jE8+yjMjzPDzfzJaXVK1A==", + "license": "MIT", + "dependencies": { + "@fluentui/react-field": "^9.4.11", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-shared-contexts": { + "version": "9.26.0", + "resolved": "https://registry.npmjs.org/@fluentui/react-shared-contexts/-/react-shared-contexts-9.26.0.tgz", + "integrity": "sha512-r52B+LUevs930pe45pFsppM9XNvY+ojgRgnDE+T/6aiwR/Mo4YoGrtjhLEzlQBeTGuySICTeaAiXfuH6Keo5Dg==", + "license": "MIT", + "dependencies": { + "@fluentui/react-theme": "^9.2.0", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "react": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-skeleton": { + "version": "9.4.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-skeleton/-/react-skeleton-9.4.11.tgz", + "integrity": "sha512-nw6NlTBXS7lNSxsebLuADYQi9gJ83jFBFsFq+AGIpAoZLBOCHOhk8/XwV3vYtPwVrKcZtOtXqh9NdCqTR3OAIA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-field": "^9.4.11", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-slider": { + "version": "9.5.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-slider/-/react-slider-9.5.11.tgz", + "integrity": "sha512-kxZKklJbcG/521muQaIDMdcftoClbwV7yMOcu8PMG+VXsaIuoandoBleBYdzM2XdpY62iK6vUPAMZWBZh3B5Ng==", + "license": "MIT", + "dependencies": { + "@fluentui/react-field": "^9.4.11", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-spinbutton": { + "version": "9.5.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-spinbutton/-/react-spinbutton-9.5.11.tgz", + "integrity": "sha512-pYR3RkJfks+0WV47KoDKD04D0pTHtT+lu3AeOpBlIswxtsb1gZEDmTrEHHNeLDKKVhWMWNoEPlxfXuX9tOh7yA==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.8", + "@fluentui/react-field": "^9.4.11", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-spinner": { + "version": "9.7.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-spinner/-/react-spinner-9.7.11.tgz", + "integrity": "sha512-MhmAisICa3BzBNQH9CnLI5NVPTTXFo1Yaey8kbQPU+gVVF4vIGORB7M1MXSHFxZvojtFpBixiVHqRwh9mowJww==", + "license": "MIT", + "dependencies": { + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-label": "^9.3.11", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-swatch-picker": { + "version": "9.4.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-swatch-picker/-/react-swatch-picker-9.4.11.tgz", + "integrity": "sha512-M/ZfHqo63F69y2ymEQDDN/BZuI3afeW3U+omyGZZoHts3rVCjPk6sKFemTRpUhGgGfxBdexWEPithZx3dk0IPA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-context-selector": "^9.2.12", + "@fluentui/react-field": "^9.4.11", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.8.0 <20.0.0", + "@types/react-dom": ">=16.8.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.8.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-switch": { + "version": "9.4.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-switch/-/react-switch-9.4.11.tgz", + "integrity": "sha512-/WDcoVFQ3I2fe5FTINfyVTIW6wuTgM5QkJgcwbU7HTANq/+wJ2f8wzywoI4x16cJOckBdy+ByDpW7uJ/Uvs8RA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-field": "^9.4.11", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-label": "^9.3.11", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-table": { + "version": "9.19.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-table/-/react-table-9.19.5.tgz", + "integrity": "sha512-In9egEdytjFd6N1RBZd5+3UgdXvEVDP7rz+/I79J10ui2+Nb7r9ah68m5CQB15AKA8F5XFDWPEGvGG3Tmuq4Jg==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.8", + "@fluentui/react-aria": "^9.17.6", + "@fluentui/react-avatar": "^9.9.12", + "@fluentui/react-checkbox": "^9.5.11", + "@fluentui/react-context-selector": "^9.2.12", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-radio": "^9.5.11", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-tabs": { + "version": "9.10.7", + "resolved": "https://registry.npmjs.org/@fluentui/react-tabs/-/react-tabs-9.10.7.tgz", + "integrity": "sha512-Kfq6GxZXEKsMdGKmHWNMcEYOYHxl5+fXJOH6ZRgeR2FkHUsPUUe2BHaFnOMRSvCwzECvhOMYs+Ekqt7JzW3BWQ==", + "license": "MIT", + "dependencies": { + "@fluentui/react-context-selector": "^9.2.12", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-tabster": { + "version": "9.26.10", + "resolved": "https://registry.npmjs.org/@fluentui/react-tabster/-/react-tabster-9.26.10.tgz", + "integrity": "sha512-KrddtwbnbgYVAnOkx1pQsMMgq7Kfi+lMRrUrDDJ9Y5X6wiXiajbWRRxYgKiOJc3MpeDCaTCEtjOWNG92vcinMw==", + "license": "MIT", + "dependencies": { + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1", + "keyborg": "^2.6.0", + "tabster": "^8.5.5" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-tag-picker": { + "version": "9.7.12", + "resolved": "https://registry.npmjs.org/@fluentui/react-tag-picker/-/react-tag-picker-9.7.12.tgz", + "integrity": "sha512-OJucCDub6b3ceGL6v2UXL+SD3x6nJMbmJ70v38BmrA9t3fNcDvn6RnsfHhF2O0pRGGUOrXbK7vDwVhUAG4Py8w==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.8", + "@fluentui/react-aria": "^9.17.6", + "@fluentui/react-combobox": "^9.16.12", + "@fluentui/react-context-selector": "^9.2.12", + "@fluentui/react-field": "^9.4.11", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-portal": "^9.8.8", + "@fluentui/react-positioning": "^9.20.10", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-tags": "^9.7.12", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-tags": { + "version": "9.7.12", + "resolved": "https://registry.npmjs.org/@fluentui/react-tags/-/react-tags-9.7.12.tgz", + "integrity": "sha512-G7pxP0GGa6J/7mYvB9ycOmD9Jpm6ByUz6JsJI4OBL9UnhenUVTtE7ZKJ9GJ0SiG0GVxS152aSlOR7NLHV7mCqw==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.8", + "@fluentui/react-aria": "^9.17.6", + "@fluentui/react-avatar": "^9.9.12", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-teaching-popover": { + "version": "9.6.12", + "resolved": "https://registry.npmjs.org/@fluentui/react-teaching-popover/-/react-teaching-popover-9.6.12.tgz", + "integrity": "sha512-Ugo5SQ3yzSlxUWkeeEdumTWTw662KDh3UPc6RGhU0Jq13skpmsClSJL678BZwsYdAaJXvvG9Bi4PjPeezeB/SA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-aria": "^9.17.6", + "@fluentui/react-button": "^9.6.12", + "@fluentui/react-context-selector": "^9.2.12", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-popover": "^9.12.12", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "@types/react": ">=16.8.0 <20.0.0", + "@types/react-dom": ">=16.8.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.8.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-text": { + "version": "9.6.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-text/-/react-text-9.6.11.tgz", + "integrity": "sha512-U7EiCesOWjkALf7LM6sy+yvE59Px3c6f27jg4aa21UMo61HCVNbjKV8Lz6GzEftEvv++/EZ25yZBiQcKgh/5iA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-textarea": { + "version": "9.6.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-textarea/-/react-textarea-9.6.11.tgz", + "integrity": "sha512-5ds8u8hzSqj8cOy0e7HJWjUMq1aO0MIJiaNt/SyIxoZFvsklj/2yaMRVXpWxr3GvX5bzScvFoBY53gPdLKtE/g==", + "license": "MIT", + "dependencies": { + "@fluentui/react-field": "^9.4.11", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-theme": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@fluentui/react-theme/-/react-theme-9.2.0.tgz", + "integrity": "sha512-Q0zp/MY1m5RjlkcwMcjn/PQRT2T+q3bgxuxWbhgaD07V+tLzBhGROvuqbsdg4YWF/IK21zPfLhmGyifhEu0DnQ==", + "license": "MIT", + "dependencies": { + "@fluentui/tokens": "1.0.0-alpha.22", + "@swc/helpers": "^0.5.1" + } + }, + "node_modules/@fluentui/react-toast": { + "version": "9.7.9", + "resolved": "https://registry.npmjs.org/@fluentui/react-toast/-/react-toast-9.7.9.tgz", + "integrity": "sha512-PaFh2CwVK4tgvRzBMb46ODHsB+ZYSYE8mx735vqgIG8Oj1AL3wZ5Y9TrjJGxn/lppZgtnwLgt4GQ+GI7MM+e+g==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.8", + "@fluentui/react-aria": "^9.17.6", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-motion": "^9.11.4", + "@fluentui/react-motion-components-preview": "^0.14.1", + "@fluentui/react-portal": "^9.8.8", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-toolbar": { + "version": "9.6.12", + "resolved": "https://registry.npmjs.org/@fluentui/react-toolbar/-/react-toolbar-9.6.12.tgz", + "integrity": "sha512-AuOZvp6Jcc/Sngk0OddTsHlJVU/u9mVEw6JDhsCYiwKeq04kdgfco1sjSTGjDhJbf1SnkhmyR6YN16SrpVQWtA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-button": "^9.6.12", + "@fluentui/react-context-selector": "^9.2.12", + "@fluentui/react-divider": "^9.4.11", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-radio": "^9.5.11", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-tooltip": { + "version": "9.8.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-tooltip/-/react-tooltip-9.8.11.tgz", + "integrity": "sha512-ke7Hbom3dtC3f9QjJG/F7QfNfukwTtAhoYLmwwQnXYTh/CIVxoC2rVh4c/V8jUD0lnjNPBZZ5ttVUopWljHuFg==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.8", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-portal": "^9.8.8", + "@fluentui/react-positioning": "^9.20.10", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-tree": { + "version": "9.15.6", + "resolved": "https://registry.npmjs.org/@fluentui/react-tree/-/react-tree-9.15.6.tgz", + "integrity": "sha512-L/uc+SgwXW8DXgSZsyIg5tQkixfrGllANg0I2578WRlfOkERehkg1eSW8Uib/Mbk+W3tB0I8CL20ifoSTL7Ztw==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.8", + "@fluentui/react-aria": "^9.17.6", + "@fluentui/react-avatar": "^9.9.12", + "@fluentui/react-button": "^9.6.12", + "@fluentui/react-checkbox": "^9.5.11", + "@fluentui/react-context-selector": "^9.2.12", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-motion": "^9.11.4", + "@fluentui/react-motion-components-preview": "^0.14.1", + "@fluentui/react-radio": "^9.5.11", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-tabster": "^9.26.10", + "@fluentui/react-theme": "^9.2.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-utilities": { + "version": "9.25.4", + "resolved": "https://registry.npmjs.org/@fluentui/react-utilities/-/react-utilities-9.25.4.tgz", + "integrity": "sha512-vvEIFTfqkcBnKNJhlm8csdGNtOWDWDkqAM4tGlW7jLlFrhNkOfDsqdNuBElENPNJ1foHyVTF5ZSr20kVoKWPjQ==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.8", + "@fluentui/react-shared-contexts": "^9.26.0", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "react": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/react-virtualizer": { + "version": "9.0.0-alpha.107", + "resolved": "https://registry.npmjs.org/@fluentui/react-virtualizer/-/react-virtualizer-9.0.0-alpha.107.tgz", + "integrity": "sha512-zpTVzJB2BUNv7QdTUlLSBMCbt/EfALRuls/u/8FYaO4PGOFVeS3equytyxSOizz9zJZVhm8sjdp326DEQNiaPA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-jsx-runtime": "^9.3.3", + "@fluentui/react-shared-contexts": "^9.26.0", + "@fluentui/react-utilities": "^9.25.4", + "@griffel/react": "^1.5.32", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <20.0.0", + "@types/react-dom": ">=16.9.0 <20.0.0", + "react": ">=16.14.0 <20.0.0", + "react-dom": ">=16.14.0 <20.0.0" + } + }, + "node_modules/@fluentui/tokens": { + "version": "1.0.0-alpha.22", + "resolved": "https://registry.npmjs.org/@fluentui/tokens/-/tokens-1.0.0-alpha.22.tgz", + "integrity": "sha512-i9fgYyyCWFRdUi+vQwnV6hp7wpLGK4p09B+O/f2u71GBXzPuniubPYvrIJYtl444DD6shLjYToJhQ1S6XTFwLg==", + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.1" + } + }, + "node_modules/@griffel/core": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@griffel/core/-/core-1.19.2.tgz", + "integrity": "sha512-WkB/QQkjy9dE4vrNYGhQvRRUHFkYVOuaznVOMNTDT4pS9aTJ9XPrMTXXlkpcwaf0D3vNKoerj4zAwnU2lBzbOg==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.0", + "@griffel/style-types": "^1.3.0", + "csstype": "^3.1.3", + "rtl-css-js": "^1.16.1", + "stylis": "^4.2.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@griffel/react": { + "version": "1.5.32", + "resolved": "https://registry.npmjs.org/@griffel/react/-/react-1.5.32.tgz", + "integrity": "sha512-jN3SmSwAUcWFUQuQ9jlhqZ5ELtKY21foaUR0q1mJtiAeSErVgjkpKJyMLRYpvaFGWrDql0Uz23nXUogXbsS2wQ==", + "license": "MIT", + "dependencies": { + "@griffel/core": "^1.19.2", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "react": ">=16.8.0 <20.0.0" + } + }, + "node_modules/@griffel/style-types": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@griffel/style-types/-/style-types-1.3.0.tgz", + "integrity": "sha512-bHwD3sUE84Xwv4dH011gOKe1jul77M1S6ZFN9Tnq8pvZ48UMdY//vtES6fv7GRS5wXYT4iqxQPBluAiYAfkpmw==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "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, + "license": "MIT" + }, + "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/@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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz", + "integrity": "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz", + "integrity": "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz", + "integrity": "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz", + "integrity": "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz", + "integrity": "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz", + "integrity": "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz", + "integrity": "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz", + "integrity": "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz", + "integrity": "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz", + "integrity": "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz", + "integrity": "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz", + "integrity": "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz", + "integrity": "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz", + "integrity": "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz", + "integrity": "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz", + "integrity": "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz", + "integrity": "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz", + "integrity": "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz", + "integrity": "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz", + "integrity": "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz", + "integrity": "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz", + "integrity": "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz", + "integrity": "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "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, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.5.tgz", + "integrity": "sha512-keKxkZMqnDicuvFoJbzrhbtdLSPhj/rZThDlKWCDbgXmUg0rEUFtRssDXKYmtXluZlIqiC5VqkCgRwzuyLHKHw==", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.4.tgz", + "integrity": "sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.46.4", + "@typescript-eslint/type-utils": "8.46.4", + "@typescript-eslint/utils": "8.46.4", + "@typescript-eslint/visitor-keys": "8.46.4", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.46.4", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.4.tgz", + "integrity": "sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.46.4", + "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/typescript-estree": "8.46.4", + "@typescript-eslint/visitor-keys": "8.46.4", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.4.tgz", + "integrity": "sha512-nPiRSKuvtTN+no/2N1kt2tUh/HoFzeEgOm9fQ6XQk4/ApGqjx0zFIIaLJ6wooR1HIoozvj2j6vTi/1fgAz7UYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.46.4", + "@typescript-eslint/types": "^8.46.4", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.4.tgz", + "integrity": "sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/visitor-keys": "8.46.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.4.tgz", + "integrity": "sha512-+/XqaZPIAk6Cjg7NWgSGe27X4zMGqrFqZ8atJsX3CWxH/jACqWnrWI68h7nHQld0y+k9eTTjb9r+KU4twLoo9A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.4.tgz", + "integrity": "sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/typescript-estree": "8.46.4", + "@typescript-eslint/utils": "8.46.4", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.4.tgz", + "integrity": "sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.4.tgz", + "integrity": "sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.46.4", + "@typescript-eslint/tsconfig-utils": "8.46.4", + "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/visitor-keys": "8.46.4", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.4.tgz", + "integrity": "sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.46.4", + "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/typescript-estree": "8.46.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.4.tgz", + "integrity": "sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.4", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.1.tgz", + "integrity": "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.47", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "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/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "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/baseline-browser-mapping": { + "version": "2.8.28", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.28.tgz", + "integrity": "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "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_modules/browserslist": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001754", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz", + "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/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" + }, + "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/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/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, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.1.tgz", + "integrity": "sha512-98XGutrXoh75MlgLihlNxAGbUuFQc7l1cqcnEZlLNKc0UrVdPndgmaDmYTDDh929VS/eqTZV0rozmhu2qqT1/g==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.254", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.254.tgz", + "integrity": "sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg==", + "dev": true, + "license": "ISC" + }, + "node_modules/embla-carousel": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", + "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", + "license": "MIT", + "peer": true + }, + "node_modules/embla-carousel-autoplay": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel-autoplay/-/embla-carousel-autoplay-8.6.0.tgz", + "integrity": "sha512-OBu5G3nwaSXkZCo1A6LTaFMZ8EpkYbwIaH+bPqdBnDGQ2fh4+NbzjXjs2SktoPNKCtflfVMc75njaDHOYXcrsA==", + "license": "MIT", + "peerDependencies": { + "embla-carousel": "8.6.0" + } + }, + "node_modules/embla-carousel-fade": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel-fade/-/embla-carousel-fade-8.6.0.tgz", + "integrity": "sha512-qaYsx5mwCz72ZrjlsXgs1nKejSrW+UhkbOMwLgfRT7w2LtdEB03nPRI06GHuHv5ac2USvbEiX2/nAHctcDwvpg==", + "license": "MIT", + "peerDependencies": { + "embla-carousel": "8.6.0" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz", + "integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "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/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/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==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "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", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "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": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyborg": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/keyborg/-/keyborg-2.6.0.tgz", + "integrity": "sha512-o5kvLbuTF+o326CMVYpjlaykxqYP9DphFQZ2ZpgrvBouyvOxyEB7oqe8nOLFpiV5VCtz0D3pt8gXQYWpLpBnmA==", + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/localized-strings": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/localized-strings/-/localized-strings-2.0.3.tgz", + "integrity": "sha512-eCSC9qK+dRrU9f9xA2glwooQCM5alK//Zj0DyKswZCBAbeefyrdplp6KQiKFeeuFu9K3QbfCQ+0Kdo0cABP6Ww==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "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": ">=6" + } + }, + "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/react": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" + }, + "node_modules/react-localization": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/react-localization/-/react-localization-2.0.6.tgz", + "integrity": "sha512-+UIJ8Dm/Bfdrz38u4d6rgW2Ip9TP9uetUp1WgTrWibgPoJXnclAgPafWL26NbBzuxmOxo9UIRqOKWY/MBJmNdQ==", + "license": "MIT", + "dependencies": { + "localized-strings": "^2.0.3" + }, + "peerDependencies": { + "react": ">=19.0.0", + "react-dom": ">=19.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz", + "integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.2", + "@rollup/rollup-android-arm64": "4.53.2", + "@rollup/rollup-darwin-arm64": "4.53.2", + "@rollup/rollup-darwin-x64": "4.53.2", + "@rollup/rollup-freebsd-arm64": "4.53.2", + "@rollup/rollup-freebsd-x64": "4.53.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.2", + "@rollup/rollup-linux-arm-musleabihf": "4.53.2", + "@rollup/rollup-linux-arm64-gnu": "4.53.2", + "@rollup/rollup-linux-arm64-musl": "4.53.2", + "@rollup/rollup-linux-loong64-gnu": "4.53.2", + "@rollup/rollup-linux-ppc64-gnu": "4.53.2", + "@rollup/rollup-linux-riscv64-gnu": "4.53.2", + "@rollup/rollup-linux-riscv64-musl": "4.53.2", + "@rollup/rollup-linux-s390x-gnu": "4.53.2", + "@rollup/rollup-linux-x64-gnu": "4.53.2", + "@rollup/rollup-linux-x64-musl": "4.53.2", + "@rollup/rollup-openharmony-arm64": "4.53.2", + "@rollup/rollup-win32-arm64-msvc": "4.53.2", + "@rollup/rollup-win32-ia32-msvc": "4.53.2", + "@rollup/rollup-win32-x64-gnu": "4.53.2", + "@rollup/rollup-win32-x64-msvc": "4.53.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/rtl-css-js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz", + "integrity": "sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.1.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==", + "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", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT", + "peer": true + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tabster": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/tabster/-/tabster-8.5.6.tgz", + "integrity": "sha512-2vfrRGrx8O9BjdrtSlVA5fvpmbq5HQBRN13XFRg6LAvZ1Fr3QdBnswgT4YgFS5Bhoo5nxwgjRaRueI2Us/dv7g==", + "license": "MIT", + "dependencies": { + "keyborg": "2.6.0", + "tslib": "^2.8.1" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "4.40.0" + } + }, + "node_modules/tabster/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/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", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "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==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.4.tgz", + "integrity": "sha512-KALyxkpYV5Ix7UhvjTwJXZv76VWsHG+NjNlt/z+a17SOQSiOcBdUXdbJdyXi7RPxrBFECtFOiPwUJQusJuCqrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.46.4", + "@typescript-eslint/parser": "8.46.4", + "@typescript-eslint/typescript-estree": "8.46.4", + "@typescript-eslint/utils": "8.46.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vite": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz", + "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.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" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/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", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", + "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/app/package.json b/app/package.json new file mode 100644 index 0000000..367967e --- /dev/null +++ b/app/package.json @@ -0,0 +1,34 @@ +{ + "name": "bonch-calendar", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@fluentui/react-components": "^9.72.7", + "@fluentui/react-icons": "^2.0.314", + "@fluentui/react-motion-components-preview": "^0.14.1", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-localization": "^2.0.6" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.0", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@vitejs/plugin-react": "^5.1.0", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.46.3", + "vite": "^7.2.2" + } +} diff --git a/app/public/apple-icon.png b/app/public/apple-icon.png new file mode 100644 index 0000000..c4a1bf1 Binary files /dev/null and b/app/public/apple-icon.png differ diff --git a/app/public/bonch-calendar.svg b/app/public/bonch-calendar.svg new file mode 100644 index 0000000..af67e4d --- /dev/null +++ b/app/public/bonch-calendar.svg @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/app/public/favicon.ico b/app/public/favicon.ico new file mode 100644 index 0000000..eeb7982 Binary files /dev/null and b/app/public/favicon.ico differ diff --git a/app/public/footer.svg b/app/public/footer.svg new file mode 100644 index 0000000..73d2387 --- /dev/null +++ b/app/public/footer.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/opengraph.png b/app/public/opengraph.png new file mode 100644 index 0000000..5b87a06 Binary files /dev/null and b/app/public/opengraph.png differ diff --git a/app/public/tios.jpg b/app/public/tios.jpg new file mode 100644 index 0000000..3630f7b Binary files /dev/null and b/app/public/tios.jpg differ diff --git a/app/src/App.tsx b/app/src/App.tsx new file mode 100644 index 0000000..69f5f54 --- /dev/null +++ b/app/src/App.tsx @@ -0,0 +1,34 @@ +import { FluentProvider, makeStyles, type Theme } from "@fluentui/react-components"; +import { type ReactElement } from "react"; +import { useTheme } from "./hooks/useTheme"; +import MainView from "./views/MainView"; +import FaqView from "./views/FaqView"; +import DedicatedView from "./views/DedicatedView"; +import FooterView from "./views/FooterView"; + +export default function App(): ReactElement +{ + const theme: Theme = useTheme(); + const cls = useStyles(); + + return ( + +
+ + + + +
+
+ ); +} + +const useStyles = makeStyles({ + root: + { + display: "flex", + flexFlow: "column", + alignItems: "center", + minHeight: "100vh" + } +}) diff --git a/app/src/hooks/useTheme.ts b/app/src/hooks/useTheme.ts new file mode 100644 index 0000000..6990b21 --- /dev/null +++ b/app/src/hooks/useTheme.ts @@ -0,0 +1,49 @@ +import { webDarkTheme, webLightTheme, type Theme } from "@fluentui/react-components"; +import { useEffect, useState } from "react"; + +const media: MediaQueryList = window.matchMedia("(prefers-color-scheme: dark)"); +const getTheme = (isDark: boolean) => isDark ? darkTheme : lightTheme; + +const baseTheme: Partial = +{ + fontFamilyBase: "\"Fira Sans\", sans-serif", + colorBrandForeground1: "#f68b1f", + colorBrandStroke1: "#f68b1f", + colorBrandForegroundLink: "#f68b1f", + colorBrandForegroundLinkHover: "#c36e18", + colorBrandForegroundLinkPressed: "#a95f15", + colorBrandStroke2Contrast: "#FDE6CE", + colorBrandBackground: "#f68b1f", + colorBrandBackgroundHover: "#c36e18", + colorNeutralForeground2BrandHover: "#c36e18", + colorBrandBackgroundPressed: "#a95f15", + colorCompoundBrandStroke: "#f68b1f", + colorCompoundBrandStrokePressed: "#a95f15" +}; + +const lightTheme: Theme = +{ + ...webLightTheme, ...baseTheme, + colorNeutralForeground1: "#000000", + colorNeutralForeground2: "#4D4D4D" +}; + +const darkTheme: Theme = +{ + ...webDarkTheme, ...baseTheme, + colorNeutralBackground2: "#4D4D4D" +}; + +export function useTheme(): Theme +{ + const [theme, setTheme] = useState(getTheme(media.matches)); + + useEffect(() => + { + const updateTheme = (args: MediaQueryListEvent) => setTheme(getTheme(args.matches)); + media.addEventListener("change", updateTheme); + return () => media.removeEventListener("change", updateTheme); + }, []); + + return theme; +} diff --git a/app/src/hooks/useTimeout.ts b/app/src/hooks/useTimeout.ts new file mode 100644 index 0000000..d225636 --- /dev/null +++ b/app/src/hooks/useTimeout.ts @@ -0,0 +1,17 @@ +import { useCallback, useState } from "react"; + +export default function useTimeout(timeout: number): [boolean, () => void] +{ + const [isActive, setActive] = useState(false); + + const trigger = useCallback(() => + { + if (isActive) + return; + + setActive(true); + setTimeout(() => setActive(false), timeout); + }, [timeout, isActive]); + + return [isActive, trigger]; +} diff --git a/app/src/index.css b/app/src/index.css new file mode 100644 index 0000000..d79088c --- /dev/null +++ b/app/src/index.css @@ -0,0 +1,29 @@ +body +{ + margin: 0; + padding: 0; + box-sizing: border-box; + overflow-x: hidden; + font-family: "Fira Sans", sans-serif; + user-select: none; +} + +h1, h2, h3, ul, p +{ + margin: 0; +} + +@keyframes scaleUpFade +{ + 0% + { + opacity: 0; + transform: scale(0.8); + } + + 100% + { + opacity: 1; + transform: scale(1); + } +} diff --git a/app/src/main.tsx b/app/src/main.tsx new file mode 100644 index 0000000..c9d55ed --- /dev/null +++ b/app/src/main.tsx @@ -0,0 +1,24 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import "./index.css"; +import App from "./App.tsx"; +import strings from "./utils/strings.ts"; + +const preferredLanguages = navigator.languages.map(lang => lang.split("-")[0].toLocaleLowerCase()); + +if ( + (preferredLanguages.includes("ru")&& !window.location.pathname.startsWith("/en")) || + window.location.pathname.startsWith("/ru") +) + strings.setLanguage("ru"); +else +{ + strings.setLanguage("en"); + document.title = strings.formatString(strings.title_p1, strings.title_p2) as string; +} + +createRoot(document.getElementById("root")!).render( + + + +); diff --git a/app/src/utils/api.ts b/app/src/utils/api.ts new file mode 100644 index 0000000..980c7d0 --- /dev/null +++ b/app/src/utils/api.ts @@ -0,0 +1,11 @@ +export const fetchFaculties = async (): Promise<[string, string][]> => +{ + const res = await fetch(import.meta.env.VITE_BACKEND_HOST + "/faculties"); + return Object.entries(await res.json()); +}; + +export const fetchGroups = async (facultyId: string, course: number): Promise<[string, string][]> => +{ + const res = await fetch(`${import.meta.env.VITE_BACKEND_HOST}/groups?facultyId=${facultyId}&course=${course}`); + return Object.entries(await res.json()); +}; diff --git a/app/src/utils/extLink.tsx b/app/src/utils/extLink.tsx new file mode 100644 index 0000000..0bca613 --- /dev/null +++ b/app/src/utils/extLink.tsx @@ -0,0 +1,7 @@ +import { Link } from "@fluentui/react-components"; +import type { ReactElement } from "react"; + +const extLink = (url: string, text: string): ReactElement => + { text }; + +export default extLink; diff --git a/app/src/utils/strings.ts b/app/src/utils/strings.ts new file mode 100644 index 0000000..3711b7c --- /dev/null +++ b/app/src/utils/strings.ts @@ -0,0 +1,124 @@ +import LocalizedStrings from "react-localization"; + +const strings = new LocalizedStrings({ + en: + { + // MainView.tsx + title_p1: "Bonch.{0}", + title_p2: "Calendar", + subtitle_p1: "Check your SPbSUT classes in {0} calendar", + subtitle_p2: "your", + pickFaculty: "1. Pick your faculty", + pickCourse: "2. Pick your course", + pickGroup: "3. Pick your group", + pickGroup_empty: "No groups are available for the selected course", + subscribe: "4. Subscribe to the calendar", + copy: "Copy link", + or: "or", + download: "Download .ics file", + cta: "Like the service? Tell your classmates!", + + // FaqView.tsx + faq_h2: "Frequently asked questions", + question1_h3: "How do I save timetable to my Outlook/Google calendar?", + answer1_p1: "Once you picked your group, copy the generated link. Then, in your calendar app subscirbe to a new calendar using that link. Here're some guides:", + answer1_li1: "Google Calendar", + answer1_li2: "Outlook", + answer1_li3: "Apple iCloud", + answer1_p2: "Note that subscribing to a web calendar is available only on desktop versions of Google and Outlook. But once you subscribe, the timetable will be available on all your devices.", + question2_h3: "The timetable can change. Do I need to re-import the calendar every time?", + answer2_p1: "Unless you imported the calendar from file, no. Subscribed calendars update automatically. On our end, calendars update every six hours.", + question3_h3: "My group/faculty doesn't appear in the list. How do I get my timetable?", + answer3_p1: "If your faculty or group is missing, it probably means that the timetable for it is not yet published. Try again later.", + answer3_p2: "If you already have a calendar link, it will work once the timetable is published.", + question4_h3: "Do I need to re-import my calendar at the start of each semester/year?", + answer4_p1: "No. The generated calendar link is valid indefinitely and will always point to the latest timetable for your group.", + answer4_p2: "That being said, if your group or faculty changes their name, you might need to generate a new link, as the group and faculty IDs might change.", + question5_h3: "Does the calendar show timetable from past semesters?", + answer5_p1: "No. The calendar contains only the current semester's timetable. Once you enter a new semester, all past events will disappear.", + answer5_p2: "If you want to keep past semesters' timetables, consider downloading them as files at the end of each semester.", + question6_h3: "Something doesn't work (timetable doesn't show, the website is broken, etc.). How do I report this?", + answer6_p1: "You can file an issue on project's {0} or contact me {1} (the former is preferred).", + answer6_p1_link1: "GitHub page", + answer6_p1_link2: "via email", + answer6_p2: "Note that I am no longer a student and work on this project in my spare time. So if you want to get a fix quickly, consider submitting a pull request yourself. You can find all the necessary information on project's {0}.", + answer6_p2_link: "GitHub page", + question7_h3: "I want a propose a new feature. Do I file it on GitHub as well?", + answer7_p1: "I do not accept any feature requests for this project. However, if you want to propose a new feature, then yes, you can still file an issue on project's {0} and maybe someone else will implement it.", + answer7_p1_link: "GitHub page", + answer7_p2: "The other way is to implement the feature yourself and submit a pull request. I do welcome contributions.", + question8_h3: "GUT.Schedule app doesn't work anymore. Will it be fixed?", + answer8_p1: "GUT.Schedule application is no longer supported. This project is a successor to that app, so please use it instead.", + answer8_p2: "That being said, the GUT.Schedule app is open source as well, so if you'd like to tinker with it, you can find its source code {0}.", + answer8_p2_link: "on GitHub", + + // DedicatedView.tsx + dedicated_h2: "Dedicated to memory of Scientific and educational center \"Technologies of informational and educational systems\"", + dedicated_p: "Always in our hearts ❤️", + + // FooterView.tsx + footer_p1: "Made with ☕ and ❤️{0}by {1}", + footer_p2: "Eugene Fox" + }, + ru: + { + // MainView.tsx + title_p1: "Бонч.{0}", + title_p2: "Календарь", + subtitle_p1: "Смотри расписание СПбГУТ в {0} календаре", + subtitle_p2: "своем", + pickFaculty: "1. Выбери свой факультет", + pickCourse: "2. Выбери свой курс", + pickGroup: "3. Выбери свою группу", + pickGroup_empty: "Нет доступных групп для выбранного курса", + subscribe: "4. Подпишись на календарь", + copy: "Скопировать ссылку", + or: "или", + download: "Скачай .ics файл", + cta: "Понравился сервис? Расскажи одногруппникам!", + + // FaqView.tsx + faq_h2: "Часто задаваемые вопросы", + question1_h3: "Как сохранить расписание в Outlook/Google календарь?", + answer1_p1: "После того, как вы выбрали свою группу, скопируйте сгенерированную ссылку. Затем в своем календаре подпишитесь на новый календарь, используя эту ссылку. Вот несколько инструкций:", + answer1_li1: "Google Календарь", + answer1_li2: "Outlook", + answer1_li3: "Apple Календарь", + answer1_p2: "Обратите внимание, что в Google и Outlook подписаться на веб-календарь можно только в веб-версиях этих сервисов. Но после этого расписание будет доступно на всех ваших устройствах.", + question2_h3: "Расписание может меняться. Нужно ли мне импортировать календарь каждый раз?", + answer2_p1: "Если вы не импортировали календарь из файла, то нет. Календари на которые вы подписаны обновляются автоматически. С нашей стороны, календари обновляются каждые шесть часов.", + question3_h3: "Моя группа/факультет не отображается в списке. Как мне получить свое расписание?", + answer3_p1: "Если ваш факультет или группа отсутствует, скорее всего, расписание для них еще не опубликовано. Попробуйте позже.", + answer3_p2: "Если у вас уже есть ссылка на календарь, можете использовать ее. Расписание появится в календаре сразу как только оно будет опубликовано.", + question4_h3: "Нужно ли мне повторно импортировать календарь в начале каждого семестра/года?", + answer4_p1: "Нет. Сгенерированная ссылка на календарь действительна бессрочно и всегда будет указывать на актуальное расписание для вашей группы.", + answer4_p2: "Однако, если ваша группа или факультет изменили свое название, возможно, вам придется сгенерировать новую ссылку, так как идентификаторы группы или факультета могли также измениться.", + question5_h3: "Показывает ли календарь расписание из прошлых семестров?", + answer5_p1: "Нет. Календарь содержит только расписание текущего семестра. Как только начнется новый семестр, все прошедшие события исчезнут.", + answer5_p2: "Если вы все же хотите сохранить расписания прошлых семестров, вы можете скачивать их в виде файлов в конце каждого семестра.", + question6_h3: "Что-то не работает (расписание не отображается, сайт сломан и т.д.). Как мне об этом сообщить?", + answer6_p1: "Вы можете создать задачу на {0} проекта или связаться со мной {1} (первый вариант предпочтительнее).", + answer6_p1_link1: "странице GitHub", + answer6_p1_link2: "по электронной почте", + answer6_p2: "Обратите внимание, что я больше не являюсь студентом и работаю над этим проектом в свое свободное время. Поэтому, если вы хотите быстро получить исправление, вы можете самостоятельно создать пул реквест. Вся необходимая информация доступна на {0} проекта.", + answer6_p2_link: "странице GitHub", + question7_h3: "Я хочу предложить новую функцию. Это также делается через GitHub?", + answer7_p1: "Я не принимаю запросы на добавление функций для этого проекта. Однако, если вы хотите предложить новую функцию, то да, вы можете создать задачу на {0} проекта, и, возможно, кто-то другой ее реализует.", + answer7_p1_link: "странице GitHub", + answer7_p2: "Другой способ - реализовать функцию самостоятельно и отправить пул реквест. Я приветствую сторонний вклад в проект.", + question8_h3: "Приложение ГУТ.Расписание больше не работает. Его починят?", + answer8_p1: "Приложение ГУТ.Расписание больше не поддерживается. Этот проект является его преемником.", + answer8_p2: "Тем не менее, ГУТ.Расписание также имеет открытый исходный код, поэтому, если вы хотите с ним поэкспериментировать, вы можете найти его {0}.", + answer8_p2_link: "на GitHub", + + // DedicatedView.tsx + dedicated_h2: "Посвящается памяти научно-образовательного центра \"ТИОС\"", + dedicated_p: "Навсегда в наших сердцах ❤️", + + // FooterView.tsx + footer_p1: "Сделано с ☕ и ❤️,{0}{1}", + footer_p2: "Евгений Лис" + } +}); + +export default strings; diff --git a/app/src/views/DedicatedView.tsx b/app/src/views/DedicatedView.tsx new file mode 100644 index 0000000..2b7750c --- /dev/null +++ b/app/src/views/DedicatedView.tsx @@ -0,0 +1,37 @@ +import { Body1, Body2, makeStyles, tokens } from "@fluentui/react-components"; +import type { ReactElement } from "react"; +import strings from "../utils/strings"; + +export default function DedicatedView(): ReactElement +{ + const cls = useStyles(); + + return ( +
+ { strings.dedicated_h2 } + { strings.dedicated_p } + + + +
+ ); +} + +const useStyles = makeStyles({ + root: + { + display: "flex", + boxSizing: "border-box", + flexFlow: "column", + alignItems: "center", + gap: tokens.spacingVerticalL, + padding: `200px ${tokens.spacingHorizontalM}` + }, + image: + { + width: "100%", + maxWidth: "600px", + borderRadius: tokens.borderRadiusMedium, + boxShadow: tokens.shadow16 + } +}); diff --git a/app/src/views/FaqView.styles.ts b/app/src/views/FaqView.styles.ts new file mode 100644 index 0000000..4f3ab9b --- /dev/null +++ b/app/src/views/FaqView.styles.ts @@ -0,0 +1,32 @@ +import { makeStyles, tokens } from "@fluentui/react-components"; + +const useStyles_FaqView = makeStyles({ + root: + { + display: "flex", + flexFlow: "column", + justifyContent: "center", + gap: tokens.spacingVerticalXXXL, + width: "100%", + maxWidth: "1200px", + padding: `${tokens.spacingVerticalS} ${tokens.spacingVerticalM}`, + userSelect: "text", + boxSizing: "border-box", + marginBottom: tokens.spacingVerticalXXXL + }, + question: + { + display: "flex", + flexFlow: "column", + gap: tokens.spacingVerticalM + }, + answer: + { + display: "flex", + flexFlow: "column", + gap: tokens.spacingVerticalSNudge, + padding: `${tokens.spacingVerticalNone} ${tokens.spacingHorizontalM}` + } +}); + +export default useStyles_FaqView; diff --git a/app/src/views/FaqView.tsx b/app/src/views/FaqView.tsx new file mode 100644 index 0000000..21d7155 --- /dev/null +++ b/app/src/views/FaqView.tsx @@ -0,0 +1,97 @@ +import { Body1, Subtitle1, Title3 } from "@fluentui/react-components"; +import type { ReactElement } from "react"; +import useStyles_FaqView from "./FaqView.styles"; +import extLink from "../utils/extLink"; +import strings from "../utils/strings"; + +const GITHUB_REPO = "https://github.com/xfox111/bonch-calendar"; +const GITHUB_ISSUES = "https://github.com/xfox111/bonch-calendar/issues"; +const GOOGLE_HELP = "https://support.google.com/calendar/answer/37100"; +const OUTLOOK_HELP = "https://support.microsoft.com/office/import-or-subscribe-to-a-calendar-in-outlook-com-or-outlook-on-the-web-cff1429c-5af6-41ec-a5b4-74f2c278e98c"; +const APPLE_HELP = "https://support.apple.com/en-us/102301"; +const EMAIL = "mailto:feedback@xfox111.net"; +const GUT_SCHEDULE_REPO = "https://github.com/xfox111/GUTSchedule"; + +export default function FaqView(): ReactElement +{ + const cls = useStyles_FaqView(); + + return ( +
+ { strings.faq_h2 } + +
+ { strings.question1_h3 } +
+ { strings.answer1_p1 } +
    +
  • { extLink(GOOGLE_HELP, strings.answer1_li1) }
  • +
  • { extLink(OUTLOOK_HELP, strings.answer1_li2) }
  • +
  • { extLink(APPLE_HELP, strings.answer1_li3) }
  • +
+ { strings.answer1_p2 } +
+
+
+ { strings.question2_h3 } +
+ { strings.answer2_p1 } +
+
+
+ { strings.question3_h3 } +
+ { strings.answer3_p1 } + { strings.answer3_p2 } +
+
+
+ { strings.question4_h3 } +
+ { strings.answer4_p1 } + { strings.answer4_p2 } +
+
+
+ { strings.question5_h3 } +
+ { strings.answer5_p1 } + { strings.answer5_p2 } +
+
+
+ { strings.question6_h3 } +
+ + { strings.formatString( + strings.answer6_p1, + extLink(GITHUB_ISSUES, strings.answer6_p1_link1), + extLink(EMAIL, strings.answer6_p1_link2) + ) } + + + { strings.formatString(strings.answer6_p2, extLink(GITHUB_REPO, strings.answer6_p2_link)) } + +
+
+
+ { strings.question7_h3 } +
+ + { strings.formatString(strings.answer7_p1, extLink(GITHUB_REPO, strings.answer7_p1_link)) } + + { strings.answer7_p2} +
+
+
+ { strings.question8_h3 } +
+ { strings.answer8_p1 } + + { strings.formatString(strings.answer8_p2, extLink(GUT_SCHEDULE_REPO, strings.answer8_p2_link)) } + +
+
+
+ ); +} diff --git a/app/src/views/FooterView.tsx b/app/src/views/FooterView.tsx new file mode 100644 index 0000000..ea900c5 --- /dev/null +++ b/app/src/views/FooterView.tsx @@ -0,0 +1,44 @@ +import { Body1, makeStyles } from "@fluentui/react-components"; +import type { ReactElement } from "react"; +import extLink from "../utils/extLink"; +import strings from "../utils/strings"; + +const MY_WEBSITE = "https://xfox111.net"; + +export default function FooterView(): ReactElement +{ + const cls = useStyles(); + + return ( +
+
+ + {strings.formatString(strings.footer_p1,
, extLink(MY_WEBSITE, strings.footer_p2))} +
+ +
+
+ ); +} + +const useStyles = makeStyles({ + root: + { + width: "100%", + display: "flex", + justifyContent: "flex-end", + alignItems: "end" + }, + imageContainer: + { + position: "relative", + width: "100%", + maxWidth: "400px", + }, + caption: + { + position: "absolute", + top: "24px", + left: "72px", + } +}); diff --git a/app/src/views/MainView.styles.ts b/app/src/views/MainView.styles.ts new file mode 100644 index 0000000..f2241ce --- /dev/null +++ b/app/src/views/MainView.styles.ts @@ -0,0 +1,81 @@ +import { makeStyles, tokens, shorthands } from "@fluentui/react-components"; + + +const useStyles_MainView = makeStyles({ + root: + { + display: "flex", + flexFlow: "column", + gap: tokens.spacingVerticalXXXL, + justifyContent: "center", + minHeight: "90vh", + alignItems: "center", + padding: `${tokens.spacingVerticalL} ${tokens.spacingHorizontalL}`, + + "& p": + { + textAlign: "center" + } + }, + highlight: + { + color: tokens.colorBrandForeground1 + }, + courseButton: + { + minWidth: "48px", + borderRadius: tokens.borderRadiusNone, + borderRightWidth: 0, + + "&:first-of-type": + { + borderStartStartRadius: tokens.borderRadiusCircular, + borderEndStartRadius: tokens.borderRadiusCircular + }, + + "&:last-of-type": + { + borderStartEndRadius: tokens.borderRadiusCircular, + borderEndEndRadius: tokens.borderRadiusCircular, + borderRightWidth: tokens.strokeWidthThin, + }, + }, + stack: + { + display: "flex", + flexFlow: "column", + alignItems: "center", + gap: tokens.spacingVerticalSNudge + }, + form: + { + gap: tokens.spacingVerticalL + }, + copiedStyle: + { + color: tokens.colorStatusSuccessForeground1 + " !important", + ...shorthands.borderColor(tokens.colorStatusSuccessBorder1 + " !important") + }, + field: + { + width: "250px" + }, + copyIcon: + { + animationName: "scaleUpFade", + animationDuration: tokens.durationFast, + animationTimingFunction: tokens.curveEasyEaseMax + }, + hidden: + { + pointerEvents: "none" + }, + truncatedText: + { + overflowX: "hidden", + textOverflow: "ellipsis", + whiteSpace: "nowrap" + } +}); + +export default useStyles_MainView; diff --git a/app/src/views/MainView.tsx b/app/src/views/MainView.tsx new file mode 100644 index 0000000..69a6e6f --- /dev/null +++ b/app/src/views/MainView.tsx @@ -0,0 +1,167 @@ +import { LargeTitle, Subtitle1, Label, Dropdown, Button, Subtitle2, Body1, Option } from "@fluentui/react-components"; +import { mergeClasses, useArrowNavigationGroup } from "@fluentui/react-components"; +import type { SelectionEvents, OptionOnSelectData } from "@fluentui/react-components"; +import { Copy24Regular, ArrowDownload24Regular, Checkmark24Regular } from "@fluentui/react-icons"; +import { Slide, Stagger } from "@fluentui/react-motion-components-preview"; +import { use, useCallback, useMemo, useState, type ReactElement } from "react"; +import useTimeout from "../hooks/useTimeout"; +import useStyles_MainView from "./MainView.styles"; +import { fetchFaculties, fetchGroups } from "../utils/api"; +import strings from "../utils/strings"; + +const facultiesPromise = fetchFaculties(); + +const getEntryOrEmpty = (entries: [string, string][], key: string): string => + entries.find(i => i[0] === key)?.[1] ?? ""; + +export default function MainView(): ReactElement +{ + const faculties: [string, string][] = use(facultiesPromise); + const [facultyId, setFacultyId] = useState(""); + + const courses: number[] = useMemo(() => facultyId == "56682" ? [1, 2] : [1, 2, 3, 4, 5], [facultyId]); + const [course, setCourse] = useState(0); + + const [groups, setGroups] = useState<[string, string][] | null>(null); + const [groupId, setGroupId] = useState(""); + + const icalUrl = useMemo(() => `${import.meta.env.VITE_BACKEND_HOST}/timetable/${facultyId}/${groupId}`, [groupId, facultyId]); + + const [showCta, setShowCta] = useState(false); + const [copyActive, triggerCopy] = useTimeout(3000); + + const navAttributes = useArrowNavigationGroup({ axis: "horizontal" }); + const cls = useStyles_MainView(); + + const copyLink = useCallback((): void => + { + navigator.clipboard.writeText(icalUrl); + triggerCopy(); + setShowCta(true); + }, [icalUrl, triggerCopy]); + + const onFacultySelect = useCallback((_: SelectionEvents, data: OptionOnSelectData): void => + { + if (data.optionValue === facultyId) + return; + + setFacultyId(data.optionValue!); + setCourse(0); + setGroupId(""); + setGroups(null); + }, [facultyId]); + + const onCourseSelect = useCallback((courseNumber: number): void => + { + if (courseNumber === course) + return; + + setCourse(courseNumber); + setGroupId(""); + setGroups(null); + fetchGroups(facultyId, courseNumber).then(setGroups); + }, [course, facultyId]); + + return ( +
+
+ + { strings.formatString(strings.title_p1, { strings.title_p2 }) } + + + { strings.formatString(strings.subtitle_p1, { strings.subtitle_p2 }) } + +
+
+ +
+ + { getEntryOrEmpty(faculties, facultyId) } + }> + + { faculties.map(([id, name]) => + + ) } + +
+
+ +
+ +
+ { courses.map(i => + + ) } +
+
+
+ +
+ + setGroupId(e.optionValue!) }> + + { groups?.map(([id, name]) => + + ) } + { (groups?.length ?? 0) < 1 && + + } + +
+
+
+
+ + +
+ { strings.subscribe } + +
+
+ +
+ { strings.or } + +
+
+
+
+ + { strings.cta } + +
+ ); +} diff --git a/app/tsconfig.app.json b/app/tsconfig.app.json new file mode 100644 index 0000000..0712f33 --- /dev/null +++ b/app/tsconfig.app.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "module": "ESNext", + "types": [ + "vite/client" + ], + "skipLibCheck": true, + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": [ + "src" + ] +} diff --git a/app/tsconfig.json b/app/tsconfig.json new file mode 100644 index 0000000..a5b06bf --- /dev/null +++ b/app/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.node.json" + } + ] +} diff --git a/app/tsconfig.node.json b/app/tsconfig.node.json new file mode 100644 index 0000000..335a3d5 --- /dev/null +++ b/app/tsconfig.node.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": [ + "ES2023" + ], + "module": "ESNext", + "types": [ + "node" + ], + "skipLibCheck": true, + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": [ + "vite.config.ts" + ] +} diff --git a/app/vite.config.ts b/app/vite.config.ts new file mode 100644 index 0000000..eaebd43 --- /dev/null +++ b/app/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}); diff --git a/assets/dark.png b/assets/dark.png new file mode 100644 index 0000000..8958e41 Binary files /dev/null and b/assets/dark.png differ diff --git a/assets/light.png b/assets/light.png new file mode 100644 index 0000000..15df5d4 Binary files /dev/null and b/assets/light.png differ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..806a58d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +services: + api: + image: xfox111/bonch-calendar-api:latest + build: + context: ./api + ports: + - 8080:8080 + + app: + image: xfox111/bonch-calendar-app:latest + build: + context: ./app + args: + - API_HOST=http://localhost:8080 + ports: + - 8000:80 + depends_on: + - api