commit 45b96ec20f4f82daa2a686ea52194571b9ebdff9 Author: ToxicCrzay270 <185776014+ToxicCrzay270@users.noreply.github.com> Date: Fri May 15 00:41:38 2026 +0200 Initialize League GUI prototype diff --git a/.codex/project.md b/.codex/project.md new file mode 100644 index 0000000..04f7325 --- /dev/null +++ b/.codex/project.md @@ -0,0 +1,79 @@ +# Codex Project Notes + +## Project + +`League of Legends GUI Overhaul` is a React/Vite prototype for a modern, dark, MOBA-/fantasy-inspired client interface. + +Repository: + +```text +Toxic/league-of-legends-gui-overhaul +``` + +Remote: + +```text +https://git.wilkensxl.de/Toxic/league-of-legends-gui-overhaul.git +``` + +## Commands + +Use these commands as the source of truth: + +```bash +npm install +npm run dev +npm run build +npm test +git diff --check +``` + +There is no separate lint script yet. `npm run build` runs `tsc --noEmit` before the Vite build. There is no audit or release-check script yet. + +## Stack + +```text +React, Vite, TypeScript, React Router, Vitest, Testing Library, CSS custom properties. +``` + +Package manager or build tool: + +```text +npm +``` + +## Build Artifacts + +Release artifacts are produced in: + +Expected files: + +```text +dist/ +``` + +## Security Rules + +- Do not commit secrets, tokens, `.env` files, certificates, or private keys. +- Treat generated credentials as sensitive. +- Prefer local generation and local processing for user data. +- Keep dependency audit results visible in CI once dependencies exist. +- Do not add external network calls unless the feature explicitly requires them. +- Document any external assets, API calls, telemetry, or package publishing behavior when implementation begins. + +## Release Rules + +No published release process exists yet. + +Before a release: + +1. run `npm run build`, +2. run `npm test`, +3. run the release checklist, +4. verify CI is green, +5. verify download links when publishing artifacts, +6. update README and changelog, +7. create a tag, +8. create the release. + +Do not create releases unless the user explicitly asks for a release. diff --git a/.gitea/workflows/dependency-check.yml b/.gitea/workflows/dependency-check.yml new file mode 100644 index 0000000..f601304 --- /dev/null +++ b/.gitea/workflows/dependency-check.yml @@ -0,0 +1,114 @@ +name: Scheduled Dependency Check + +on: + schedule: + - cron: "29 3 * * 2" + workflow_dispatch: + +jobs: + dependency-check: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Detect project stack + id: detect + shell: bash + run: | + stacks="" + + [ -f package.json ] && stacks="${stacks} node" + { [ -f pyproject.toml ] || [ -f requirements.txt ]; } && stacks="${stacks} python" + [ -f Cargo.toml ] && stacks="${stacks} rust" + [ -f go.mod ] && stacks="${stacks} go" + { [ -f Dockerfile ] || [ -f compose.yml ] || [ -f docker-compose.yml ]; } && stacks="${stacks} docker" + + echo "stacks=${stacks:-generic}" >> "$GITHUB_OUTPUT" + echo "Detected stacks:${stacks:- generic}" + + - name: Node dependency report + if: contains(steps.detect.outputs.stacks, 'node') + shell: bash + run: | + if [ -f package-lock.json ] || [ -f npm-shrinkwrap.json ]; then + npm ci + else + npm install --package-lock-only --ignore-scripts + fi + + echo "Security audit:" + npm audit --omit=dev --audit-level=high + + echo + echo "Outdated dependencies:" + npm outdated || true + + - name: Python dependency report + if: contains(steps.detect.outputs.stacks, 'python') + shell: bash + run: | + python -m pip install --upgrade pip pip-audit + + echo "Security audit:" + if [ -f requirements.txt ]; then + pip-audit -r requirements.txt + else + pip-audit + fi + + echo + echo "Outdated packages:" + python -m pip list --outdated || true + + - name: Rust dependency report + if: contains(steps.detect.outputs.stacks, 'rust') + shell: bash + run: | + cargo install cargo-audit cargo-outdated --locked + + echo "Security audit:" + cargo audit + + echo + echo "Outdated crates:" + cargo outdated || true + + - name: Go dependency report + if: contains(steps.detect.outputs.stacks, 'go') + shell: bash + run: | + go install golang.org/x/vuln/cmd/govulncheck@latest + + echo "Security audit:" + govulncheck ./... + + echo + echo "Available dependency updates:" + go list -u -m all || true + + - name: Docker base image report + if: contains(steps.detect.outputs.stacks, 'docker') + shell: bash + run: | + echo "Docker image references:" + grep -RInE --exclude-dir=.git --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=build '^\s*FROM\s+' Dockerfile* . 2>/dev/null || true + + echo + echo "Review Docker base images manually for pinned versions, official sources, and current security status." + + - name: Dependency guidance + shell: bash + run: | + cat <<'EOF' + Dependency check completed. + + This workflow reports vulnerabilities and available updates. It does + not modify dependency files, create pull requests, or publish packages. + + Recommended manual follow-up: + - update dependencies in a focused branch, + - run the project test/build commands, + - review lockfile diffs carefully, + - document intentionally held versions. + EOF diff --git a/.gitea/workflows/release-dry-run.yml b/.gitea/workflows/release-dry-run.yml new file mode 100644 index 0000000..25e61ab --- /dev/null +++ b/.gitea/workflows/release-dry-run.yml @@ -0,0 +1,133 @@ +name: Release Dry Run + +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + release-dry-run: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Inspect release metadata + shell: bash + run: | + missing=0 + + required_docs=( + "README.md" + "CHANGELOG.md" + "SECURITY.md" + "docs/release-checklist.md" + ) + + for file in "${required_docs[@]}"; do + if [ ! -f "$file" ]; then + echo "Missing release document: $file" + missing=1 + fi + done + + placeholder_paths=(README.md AGENTS.md .codex docs) + placeholder_pattern='PROJECT_NAME|PROJECT_DESCRIPTION|REPOSITORY_OWNER|REPOSITORY_NAME|PACKAGE_NAME|ARTIFACT_NAME|ARTIFACT_OUTPUT_DIRECTORY|DOWNLOAD_URL|BUILD_COMMAND|TEST_COMMAND|LINT_COMMAND|AUDIT_COMMAND' + + for path in "${placeholder_paths[@]}"; do + [ -e "$path" ] || continue + if grep -RInE --exclude-dir=.git "$placeholder_pattern" "$path"; then + echo "Unresolved template placeholders found." + missing=1 + fi + done + + if [ "$missing" -eq 1 ]; then + exit 1 + fi + + - name: Detect project stack + id: detect + shell: bash + run: | + stacks="" + + [ -f package.json ] && stacks="${stacks} node" + { [ -f pyproject.toml ] || [ -f requirements.txt ]; } && stacks="${stacks} python" + [ -f Cargo.toml ] && stacks="${stacks} rust" + [ -f go.mod ] && stacks="${stacks} go" + + echo "stacks=${stacks:-generic}" >> "$GITHUB_OUTPUT" + echo "Detected stacks:${stacks:- generic}" + + - name: Node release checks + if: contains(steps.detect.outputs.stacks, 'node') + shell: bash + run: | + if [ -f package-lock.json ] || [ -f npm-shrinkwrap.json ]; then + npm ci + else + npm install + fi + + node -e "const p=require('./package.json'); if(!p.name||!p.version){throw new Error('package.json needs name and version')}; console.log(p.name+'@'+p.version)" + + npm run lint --if-present + npm test --if-present + npm run build --if-present + npm run release:check --if-present + + - name: Python release checks + if: contains(steps.detect.outputs.stacks, 'python') + shell: bash + run: | + python -m pip install --upgrade pip + + if [ -f requirements.txt ]; then + python -m pip install -r requirements.txt + fi + + if [ -f pyproject.toml ]; then + python -m pip install build + python -m build + else + echo "No pyproject.toml found; skipped Python package build." + fi + + - name: Rust release checks + if: contains(steps.detect.outputs.stacks, 'rust') + shell: bash + run: | + cargo test + cargo build --release + + - name: Go release checks + if: contains(steps.detect.outputs.stacks, 'go') + shell: bash + run: | + go test ./... + go build ./... + + - name: Artifact report + shell: bash + run: | + echo "Potential release artifacts:" + find . \ + -path ./.git -prune -o \ + -path ./node_modules -prune -o \ + -path './dist/*' -type f -print -o \ + -path './build/*' -type f -print -o \ + -path './release/*' -type f -print -o \ + -path './target/release/*' -type f -print \ + | sed 's#^\./##' \ + | head -200 + + cat <<'EOF' + + Release dry run completed. + + This workflow verifies release readiness. It does not create tags, + releases, packages, or upload artifacts. + EOF diff --git a/.gitea/workflows/repo-cleanup.yml b/.gitea/workflows/repo-cleanup.yml new file mode 100644 index 0000000..a4b7156 --- /dev/null +++ b/.gitea/workflows/repo-cleanup.yml @@ -0,0 +1,139 @@ +name: Scheduled Repository Cleanup Check + +on: + schedule: + - cron: "43 3 * * 1" + workflow_dispatch: + +jobs: + cleanup-check: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check ignored and untracked generated files + shell: bash + run: | + echo "Ignored files that would be skipped by git:" + git status --ignored --short || true + + echo + echo "Tracked generated files check:" + generated_patterns=( + '(^|/)node_modules/' + '(^|/)dist/' + '(^|/)build/' + '(^|/)out/' + '(^|/)release/' + '(^|/)target/' + '(^|/)coverage/' + '\.log$' + '\.tmp$' + '\.temp$' + ) + + found=0 + tracked_files="$(git ls-files)" + for pattern in "${generated_patterns[@]}"; do + if echo "$tracked_files" | grep -Ei "$pattern"; then + found=1 + fi + done + + if [ "$found" -eq 1 ]; then + echo "Generated files appear to be tracked. Review .gitignore and remove generated outputs from version control if appropriate." + exit 1 + fi + + - name: Check large tracked files + shell: bash + run: | + limit_bytes="${LARGE_FILE_LIMIT_BYTES:-5242880}" + found=0 + + while IFS= read -r file; do + [ -f "$file" ] || continue + size="$(wc -c < "$file")" + if [ "$size" -gt "$limit_bytes" ]; then + echo "${file} is ${size} bytes, above limit ${limit_bytes}." + found=1 + fi + done < <(git ls-files) + + if [ "$found" -eq 1 ]; then + echo "Large tracked files found. Move release artifacts to packages/releases or document why they belong in git." + exit 1 + fi + + - name: Check local config and secret-prone files + shell: bash + run: | + found=0 + + risky_patterns=( + '^\.env$' + '^\.env\.' + '\.pfx$' + '\.p12$' + '\.pem$' + '\.key$' + '\.token$' + '(^|/)secrets/' + ) + + tracked_files="$(git ls-files)" + for pattern in "${risky_patterns[@]}"; do + if echo "$tracked_files" | grep -Ei "$pattern" | grep -vE '^\.env\.example$'; then + found=1 + fi + done + + if [ "$found" -eq 1 ]; then + echo "Secret-prone local config files are tracked. Review immediately." + exit 1 + fi + + - name: Check stale branches + shell: bash + run: | + git fetch --all --prune + + protected='^(main|master|develop|dev|release|staging|production)$' + cutoff="$(date -u -d '90 days ago' +%s)" + found=0 + + while IFS='|' read -r branch timestamp; do + branch="${branch#origin/}" + [ "$branch" = "HEAD" ] && continue + echo "$branch" | grep -Eq "$protected" && continue + + if [ "$timestamp" -lt "$cutoff" ]; then + echo "Stale remote branch candidate: ${branch}" + found=1 + fi + done < <(git for-each-ref refs/remotes/origin --format='%(refname:short)|%(committerdate:unix)') + + if [ "$found" -eq 1 ]; then + echo "Stale branch candidates found. Review manually before deleting anything." + exit 1 + fi + + - name: Cleanup guidance + shell: bash + run: | + cat <<'EOF' + Repository cleanup check completed. + + This workflow reports cleanup candidates. It does not delete branches, + packages, releases, or files automatically. + + Recommended manual follow-up: + - remove generated files from git, + - update .gitignore, + - move large artifacts to releases or package registry, + - review stale branches, + - document intentional exceptions. + EOF diff --git a/.gitea/workflows/security-scan.yml b/.gitea/workflows/security-scan.yml new file mode 100644 index 0000000..211d8ae --- /dev/null +++ b/.gitea/workflows/security-scan.yml @@ -0,0 +1,173 @@ +name: Scheduled Security Scan + +on: + schedule: + - cron: "17 3 * * 1" + workflow_dispatch: + +jobs: + security-scan: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Detect project stack + id: detect + shell: bash + run: | + stacks="" + + [ -f package.json ] && stacks="${stacks} node" + { [ -f pyproject.toml ] || [ -f requirements.txt ]; } && stacks="${stacks} python" + [ -f Cargo.toml ] && stacks="${stacks} rust" + [ -f go.mod ] && stacks="${stacks} go" + { [ -f Dockerfile ] || [ -f compose.yml ] || [ -f docker-compose.yml ]; } && stacks="${stacks} docker" + + echo "stacks=${stacks:-generic}" >> "$GITHUB_OUTPUT" + echo "Detected stacks:${stacks:- generic}" + + - name: Node production dependency audit + if: contains(steps.detect.outputs.stacks, 'node') + run: npm audit --omit=dev --audit-level=high + + - name: Python dependency audit + if: contains(steps.detect.outputs.stacks, 'python') + shell: bash + run: | + python -m pip install --upgrade pip pip-audit + if [ -f requirements.txt ]; then + pip-audit -r requirements.txt + else + pip-audit + fi + + - name: Rust dependency audit + if: contains(steps.detect.outputs.stacks, 'rust') + shell: bash + run: | + cargo install cargo-audit --locked + cargo audit + + - name: Go vulnerability scan + if: contains(steps.detect.outputs.stacks, 'go') + shell: bash + run: | + go install golang.org/x/vuln/cmd/govulncheck@latest + govulncheck ./... + + - name: Suspicious code pattern scan + shell: bash + run: | + grep_excludes=( + --exclude-dir=.git + --exclude-dir=node_modules + --exclude-dir=dist + --exclude-dir=build + --exclude-dir=release + --exclude=security-scan.yml + ) + + patterns=( + 'eval\s*\(' + 'new Function\s*\(' + 'dangerouslySetInnerHTML' + 'innerHTML\s*=' + 'child_process' + 'exec\s*\(' + 'spawn\s*\(' + 'shell\.openExternal' + 'nodeIntegration:\s*true' + 'webSecurity:\s*false' + 'allowRunningInsecureContent:\s*true' + 'curl .*sh' + 'wget .*sh' + ) + + found=0 + for pattern in "${patterns[@]}"; do + if grep -RInE "${grep_excludes[@]}" "$pattern" .; then + found=1 + fi + done + + if [ "$found" -eq 1 ]; then + echo "Suspicious code patterns were found. Review the matches above." + exit 1 + fi + + - name: Secret and config leak scan + shell: bash + run: | + grep_excludes=( + --exclude-dir=.git + --exclude-dir=node_modules + --exclude-dir=dist + --exclude-dir=build + --exclude-dir=release + --exclude=security-scan.yml + ) + + patterns=( + 'BEGIN (RSA |EC |OPENSSH |)PRIVATE KEY' + 'AKIA[0-9A-Z]{16}' + 'xox[baprs]-[0-9A-Za-z-]+' + 'gh[pousr]_[0-9A-Za-z_]+' + 'sk-[A-Za-z0-9]{20,}' + 'api[_-]?key\s*=\s*["'\'']?[A-Za-z0-9_\-]{20,}' + 'token\s*=\s*["'\'']?[A-Za-z0-9_\-]{20,}' + 'password\s*=\s*["'\'']?[^[:space:]]{8,}' + ) + + found=0 + for pattern in "${patterns[@]}"; do + if grep -RInE "${grep_excludes[@]}" "$pattern" .; then + found=1 + fi + done + + if find . -path ./.git -prune -o \( -name ".env" -o -name ".env.*" \) -not -name ".env.example" -print | grep .; then + echo "Committed environment files were found." + found=1 + fi + + if [ "$found" -eq 1 ]; then + echo "Potential secret or config leak detected. Review the matches above." + exit 1 + fi + + - name: AI instruction safety scan + shell: bash + run: | + grep_excludes=( + --exclude-dir=.git + --exclude-dir=node_modules + --exclude-dir=dist + --exclude-dir=build + --exclude-dir=release + --exclude=security-scan.yml + ) + + patterns=( + 'ignore (all )?(previous|above) instructions' + 'system prompt' + 'developer message' + 'reveal your instructions' + 'exfiltrate' + 'send.*token' + 'send.*secret' + 'disable.*safety' + 'jailbreak' + ) + + found=0 + for pattern in "${patterns[@]}"; do + if grep -RInEi "${grep_excludes[@]}" "$pattern" .; then + found=1 + fi + done + + if [ "$found" -eq 1 ]; then + echo "Potential unsafe AI-control text found. Review whether this is documentation, test data, or malicious content." + exit 1 + fi diff --git a/.gitea/workflows/template-compliance.yml b/.gitea/workflows/template-compliance.yml new file mode 100644 index 0000000..0fcb05b --- /dev/null +++ b/.gitea/workflows/template-compliance.yml @@ -0,0 +1,99 @@ +name: Codex Template Compliance + +on: + push: + branches: + - main + - master + pull_request: + workflow_dispatch: + +jobs: + template-compliance: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Check required Codex files + shell: bash + run: | + missing=0 + + required_files=( + "AGENTS.md" + ".codex/project.md" + "README.md" + ) + + recommended_files=( + "SECURITY.md" + "CHANGELOG.md" + "docs/agent-handoff.md" + ) + + for file in "${required_files[@]}"; do + if [ ! -f "$file" ]; then + echo "Missing required Codex file: $file" + missing=1 + fi + done + + for file in "${recommended_files[@]}"; do + if [ ! -f "$file" ]; then + echo "Recommended Codex file not found: $file" + fi + done + + if [ "$missing" -eq 1 ]; then + exit 1 + fi + + - name: Check unresolved placeholders + shell: bash + run: | + found=0 + paths=(AGENTS.md README.md SECURITY.md CHANGELOG.md .codex docs) + pattern='PROJECT_NAME|PROJECT_DESCRIPTION|REPOSITORY_OWNER|REPOSITORY_NAME|PACKAGE_NAME|ARTIFACT_NAME|ARTIFACT_OUTPUT_DIRECTORY|AUTHOR_NAME|PROJECT_STACK|DOWNLOAD_URL|BUILD_COMMAND|TEST_COMMAND|LINT_COMMAND|AUDIT_COMMAND|README_COMMAND|INSTALL_COMMAND|DEV_COMMAND|PACKAGE_MANAGER|PROJECT_VERSION' + + for path in "${paths[@]}"; do + [ -e "$path" ] || continue + if grep -RInE --exclude-dir=.git "$pattern" "$path"; then + found=1 + fi + done + + if [ "$found" -eq 1 ]; then + echo "Unresolved template placeholders found. Replace real values or mark genuinely unknown values as PENDING." + exit 1 + fi + + - name: Check workflow baseline + shell: bash + run: | + echo "Detected Gitea workflows:" + find .gitea/workflows -maxdepth 1 -type f -name '*.yml' -print 2>/dev/null || true + + if [ ! -f ".gitea/workflows/security-scan.yml" ]; then + echo "Recommended workflow missing: .gitea/workflows/security-scan.yml" + fi + + if [ ! -f ".gitea/workflows/repo-cleanup.yml" ]; then + echo "Recommended workflow missing: .gitea/workflows/repo-cleanup.yml" + fi + + - name: Compliance guidance + shell: bash + run: | + cat <<'EOF' + Codex template compliance check completed. + + This workflow verifies agent context and template hygiene. It does + not change files automatically. + + Recommended manual follow-up: + - add missing required Codex context files, + - replace unresolved placeholders, + - keep README and project context aligned, + - document intentional exceptions in .codex/project.md. + EOF diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..12b73c8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +coverage/ +.vite/ +.codex-agent-repository-kit/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..e6c0ee1 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,66 @@ +# Agent Instructions + +## Project + +League of Legends GUI Overhaul: React/Vite prototype for a modern, dark, MOBA-/fantasy-inspired client interface. + +## Repository Rules + +- Prefer existing project patterns over new abstractions. +- Keep changes scoped to the user's request. +- Do not commit secrets, `.env` files, private keys, certificates, or tokens. +- Do not rewrite history or run destructive git commands unless explicitly requested. +- Do not create a release unless explicitly requested. +- Check repository state before editing and before finishing. Preserve unrelated user changes. +- Replace applicable placeholders in copied templates. Remove non-applicable placeholder sections instead of leaving fake values. +- The current stack is Node, React, Vite, TypeScript, React Router, Vitest, Testing Library, and CSS custom properties. +- If `GITEA_TOKEN` is available locally, use it only for read-only Gitea API checks such as private repository metadata, package-read visibility, and Actions run status. Never print, commit, or store the token. +- After pushing commits that trigger a Gitea workflow, poll the workflow run until it succeeds or a concrete blocker is known. +- Repository cleanup automation must be non-destructive. Do not delete branches, packages, releases, or tracked files without explicit user approval. +- Dependency, compliance, and release dry-run automation must report findings only. Do not auto-update dependencies, auto-open PRs, create tags, publish packages, or create releases without explicit user approval. + +## Commands + +Use these commands: + +```bash +npm install +npm run dev +npm run build +npm test +git diff --check +``` + +There is no separate lint script yet. `npm run build` runs `tsc --noEmit` before the Vite build. + +Keep `.codex/project.md` and this `AGENTS.md` aligned when commands, artifact paths, or release rules change. + +## Artifacts + +Build output is produced in: + +```text +dist/ +``` + +No release package naming or download verification process exists yet. + +## Security Notes + +- Review `docs/security-review.md` before release work. +- Fill `docs/security-review.md` with actual checked commands and results when performing release-readiness work. +- Review scheduled security workflow failures before changing code. Treat matches as leads: they may be true positives, documentation examples, or test fixtures. +- Review repository cleanup workflow failures as maintenance leads. Document intentional exceptions instead of blindly deleting files. +- Review dependency and template compliance workflow failures as maintenance leads. Preserve project-specific conventions when they are documented. +- Treat generated credentials and config files as sensitive. +- Keep external network calls documented. +- Prefer local processing for user data. +- Keep CI publishing secrets in repository or organization secrets, not in tracked files. + +## Finish Checklist + +- `git diff --check` passes. +- The cheapest reliable verification command has been run, or the reason it could not run is documented. +- README, changelog, security review, and release checklist are updated when the change touches release behavior. +- `docs/agent-handoff.md` is updated when work is interrupted, risky, or spans multiple sessions. +- Any pushed Gitea workflow has been polled to success or a concrete blocker has been reported. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4cb4331 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +All notable changes to this project are documented here. + +## Unreleased + +- Added the Codex Agent Repository Kit baseline. +- Documented that the project currently has no selected implementation stack or runnable build commands. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..04c6530 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,41 @@ +# Contributing + +## Working Rules + +- Keep changes scoped to the issue or user request. +- Prefer existing project patterns. +- Do not commit secrets, generated credentials, local `.env` files, or private keys. +- Do not create releases unless explicitly requested. +- Preserve unrelated user changes. + +## Before Committing + +Run the cheapest reliable verification commands for this project. + +Current available check: + +```bash +git diff --check +``` + +Once a real stack exists, add and document lint, test, build, and audit commands. + +## Pull Requests + +Pull requests should include: + +- summary of changes, +- verification performed, +- known risks or skipped checks, +- artifact or download notes when relevant. + +## Releases + +Before release work, update: + +```text +CHANGELOG.md +docs/release-checklist.md +docs/security-review.md +README.md +``` diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..2275f96 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,119 @@ +# Installation + +This guide explains how to install and run the League of Legends GUI Overhaul prototype locally. + +The project is a React/Vite/TypeScript app. It does not install a game client, connect to Riot services, or include official Riot assets. + +## Requirements + +- Node.js with npm available in your terminal. +- Recommended npm version: 10 or newer. +- A local clone of this repository. + +Check your tools: + +```powershell +node --version +npm --version +``` + +## Install Dependencies + +Open a terminal in this project folder: + +```powershell +cd D:\Codex\Main\CodexTest\projects\league-of-legends-gui-overhaul +``` + +Install dependencies: + +```powershell +npm install +``` + +This creates `node_modules/` locally. The folder is ignored by Git. + +## Start The GUI + +Start the local development server: + +```powershell +npm run dev +``` + +Vite prints a local URL, usually: + +```text +http://localhost:5173 +``` + +Open that URL in your browser. + +## Verify The Installation + +Run the production build: + +```powershell +npm run build +``` + +Run the test suite: + +```powershell +npm test +``` + +Optional dependency audit: + +```powershell +npm audit --audit-level=moderate +``` + +## Preview A Production Build + +After `npm run build`, preview the built app: + +```powershell +npm run preview +``` + +## Common Problems + +### `npm` is not recognized + +Install Node.js from the official Node.js distribution or add an existing Node/npm installation to your `PATH`. + +After updating `PATH`, open a new terminal and check again: + +```powershell +npm --version +``` + +### Port 5173 is already in use + +Start Vite on another port: + +```powershell +npm run dev -- --port 5174 +``` + +### Dependencies changed + +If `package-lock.json` changed or dependencies were updated, reinstall: + +```powershell +npm install +``` + +## What This Installs + +The install step only installs frontend development dependencies for this prototype: + +- React +- Vite +- TypeScript +- React Router +- Vitest +- Testing Library + +It does not install any Riot software, game files, launchers, or protected assets. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6ff187a --- /dev/null +++ b/README.md @@ -0,0 +1,125 @@ +# League of Legends GUI Overhaul + +A React/Vite prototype for a modern, dark, MOBA-/fantasy-inspired client interface. The goal of iteration 1 is a stable base GUI: routing, layout, reusable components, mock data, centralized theme tokens, tests, and documentation. + +This project does not use Riot assets and is not a 1:1 clone of the official League of Legends client. + +Repository: + +```text +https://git.wilkensxl.de/Toxic/league-of-legends-gui-overhaul.git +``` + +## Tech Stack + +- React +- Vite +- TypeScript +- React Router +- Vitest +- Testing Library +- CSS Custom Properties + +## Setup + +For detailed installation steps, see [INSTALL.md](INSTALL.md). + +Install dependencies: + +```powershell +npm install +``` + +Start the dev server: + +```powershell +npm run dev +``` + +Build: + +```powershell +npm run build +``` + +Run tests: + +```powershell +npm test +``` + +## Project Structure + +```text +src/ +|-- app/ +|-- components/ +| `-- ui/ +|-- config/ +|-- data/ +|-- features/ +|-- layouts/ +|-- pages/ +|-- styles/ +|-- test/ +`-- types/ +``` + +## Extending The App + +Add a page: + +1. Create a component in `src/pages/`. +2. Register it in `src/config/routes.tsx`. +3. Add a navigation item in `src/config/navigation.ts` only if it should appear in the sidebar. + +Add a navigation item: + +1. Add an entry with `id`, `label`, `path`, and optional `iconName`, `section`, `enabled`. +2. Keep sidebar rendering generic; do not hardcode menu entries in `AppShell`. + +Adjust theme tokens: + +1. Edit `src/styles/theme.css`. +2. Add preset overrides with `[data-theme="preset-name"]`. +3. Prefer tokens over page-level hardcoded colors, spacing, shadows, or radii. + +Extend mock data: + +1. Add or edit records in `src/data/`. +2. Keep display logic in pages or feature modules, not inside mock files. + +## Iteration 1 Includes + +- App Shell with header, sidebar, content area, and status footer +- Routes for Home, Play, Champions, Profile, and Settings +- A mock champion detail route at `/champions/:championId` +- A right-side Social Sidebar powered by friend mock data +- Session-only theme presets from Settings +- Prototype Toast flows from header, settings, and social actions +- Base UI components: Button, Panel, SearchInput, Dropdown, Modal, Tabs, Tooltip, Toast +- Mock data for champions, friends, news, and match history +- Central CSS theme tokens +- Smoke tests for rendering, navigation, routing, and core UI components + +## Iteration 1 Does Not Include + +- Login +- Riot API integration +- Matchmaking +- Persistence +- Full champion detail content beyond the first placeholder route +- Plugin engine +- Final visual polish +- Official Riot or League assets + +## Next Iterations + +- Polish the App Shell visuals +- Add ability, skin, and progression modules to champion detail pages +- Expand the social sidebar with parties, invites, and richer presence +- Add persisted theme preferences +- Use Modal flows in more page-level actions +- Improve responsive behavior +- Add subtle transitions and animation states +- Plan a lightweight plugin/module extension layer diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..ee5b5b1 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,22 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| --- | --- | +| Latest project state | Yes | + +## Reporting A Vulnerability + +Report security issues privately to the project owner. + +Do not include secrets, production data, private credentials, or unreleased exploit details in public issues. + +## Project Security Principles + +- Keep secrets out of the repository. +- Do not commit `.env` files, tokens, certificates, private keys, or generated credentials. +- Prefer local processing for user data. +- Document external network calls when implementation begins. +- Keep release artifacts reproducible through CI once release artifacts exist. +- Run dependency audits before releases once dependencies exist. diff --git a/docs/agent-handoff.md b/docs/agent-handoff.md new file mode 100644 index 0000000..573a29e --- /dev/null +++ b/docs/agent-handoff.md @@ -0,0 +1,38 @@ +# Agent Handoff + +Use this file when a task spans multiple sessions, has unresolved follow-up work, or changes release behavior. + +## Current State + +```text +Codex Agent Repository Kit baseline applied. The project is a React/Vite/TypeScript prototype with npm scripts for development, build, and tests. +``` + +## Changes Made + +- Added agent instructions and project notes. +- Added security, contribution, changelog, release checklist, security review, and release notes documents. +- Added non-destructive Gitea maintenance workflows. +- Project remote set to `https://git.wilkensxl.de/Toxic/league-of-legends-gui-overhaul.git`. +- Documented current npm build and test commands. + +## Verification + +| Check | Result | +| --- | --- | +| `git diff --check` | PENDING | + +## Open Questions + +- Separate lint, audit, and release-check scripts. +- Release packaging and download target. + +## Next Steps + +- Add separate lint, audit, and release-check scripts if needed. +- Add or update build workflow around `npm run build` and `npm test`. +- Update README, `AGENTS.md`, `.codex/project.md`, and release docs when implementation begins. + +## Risks + +- Release packaging is intentionally limited until download targets are defined. diff --git a/docs/release-checklist.md b/docs/release-checklist.md new file mode 100644 index 0000000..72928f8 --- /dev/null +++ b/docs/release-checklist.md @@ -0,0 +1,38 @@ +# Release Checklist + +No release process exists yet. Complete this checklist only after the project has real build, test, audit, and artifact commands. + +## Version + +- [ ] Version number updated. +- [ ] Changelog updated. +- [ ] README updated. + +## Quality + +- [ ] Working tree is clean. +- [ ] Lint or type checks pass. +- [ ] Tests pass or missing tests are documented. +- [ ] Build succeeds in CI. + +## Security + +- [ ] Security review is current. +- [ ] Dependency audit is clean or documented. +- [ ] No secrets are committed. +- [ ] Release artifacts do not contain local config files. + +## Artifacts + +- [ ] Artifacts are produced by documented commands. +- [ ] Artifacts are uploaded. +- [ ] Download links work. +- [ ] Package registry links work if used. +- [ ] Installer, portable, or archive naming is clear. + +## Release + +- [ ] Git tag created. +- [ ] Release notes written. +- [ ] Release published. +- [ ] Post-release download smoke test completed. diff --git a/docs/release-notes.md b/docs/release-notes.md new file mode 100644 index 0000000..1174451 --- /dev/null +++ b/docs/release-notes.md @@ -0,0 +1,25 @@ +# League of Legends GUI Overhaul PENDING + +## Downloads + +No release artifacts exist yet. + +## Highlights + +- Codex Agent Repository Kit baseline applied. + +## Security + +- Dependency audit: pending until dependencies exist. +- Secret handling: secrets must not be committed. +- External network calls: pending until implementation exists. + +## Verification + +| Check | Result | +| --- | --- | +| `git diff --check` | PENDING | + +## Notes + +Release notes are a placeholder until the project has a releasable artifact. diff --git a/docs/security-review.md b/docs/security-review.md new file mode 100644 index 0000000..15cfb91 --- /dev/null +++ b/docs/security-review.md @@ -0,0 +1,54 @@ +# Security Review + +## Scope + +Project: + +```text +League of Legends GUI Overhaul +``` + +Reviewed version or commit: + +```text +PENDING +``` + +## Code Patterns Checked + +- [ ] No `eval`. +- [ ] No dynamic `Function` constructor. +- [ ] No unsafe HTML injection. +- [ ] No unexpected shell execution. +- [ ] No unexpected external network calls. +- [ ] No secrets committed. +- [ ] No unsafe file writes outside expected user-selected paths. + +## Dependency Review + +Command: + +```text +PENDING +``` + +Result: + +```text +No dependency audit exists yet because no implementation stack or dependency manifest exists. +``` + +## Runtime Review + +- [ ] Least-privilege runtime configuration. +- [ ] External URLs documented. +- [ ] Local data storage documented. +- [ ] Sensitive data is not persisted unless explicitly required. + +## Release Notes + +Known residual risks: + +```text +The implementation stack, runtime behavior, and artifact process are not defined yet. +``` diff --git a/index.html b/index.html new file mode 100644 index 0000000..c0107f2 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + League GUI Overhaul + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..42ef3a6 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2534 @@ +{ + "name": "league-of-legends-gui-overhaul", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "league-of-legends-gui-overhaul", + "version": "0.1.0", + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.30.1" + }, + "devDependencies": { + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.1.0", + "@testing-library/user-event": "^14.5.2", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^6.0.1", + "jsdom": "^25.0.1", + "typescript": "^5.6.3", + "vite": "^8.0.11", + "vitest": "^4.1.5" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "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", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@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/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.128.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.128.0.tgz", + "integrity": "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.18.tgz", + "integrity": "sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.18.tgz", + "integrity": "sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.18.tgz", + "integrity": "sha512-5Ofot8xbs+pxRHJqm9/9N/4sTQOvdrwEsmPE9pdLEEoAbdZtG6F2LMDfO1sp6ZAtXJuJV/21ew2srq3W8NXB5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.18.tgz", + "integrity": "sha512-7h8eeOTT1eyqJyx64BFCnWZpNm486hGWt2sqeLLgDxA0xI1oGZ9H7gK1S85uNGmBhkdPwa/6reTxfFFKvIsebw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.18.tgz", + "integrity": "sha512-eRcm/HVt9U/JFu5RKAEKwGQYtDCKWLiaH6wOnsSEp6NMBb/3Os8LgHZlNyzMpFVNmiiMFlfb2zEnebfzJrHFmg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-SOrT/cT4ukTmgnrEz/Hg3m7LBnuCLW9psDeMKrimRWY4I8DmnO7Lco8W2vtqPmMkbVu8iJ+g4GFLVLLOVjJ9DQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.18.tgz", + "integrity": "sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.18.tgz", + "integrity": "sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.18.tgz", + "integrity": "sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.18.tgz", + "integrity": "sha512-+J9YGmc+czgqlhYmwun3S3O0FIZhsH8ep2456xwjAdIOmuJxM7xz4P4PtrxU+Bz17a/5bqPA8o3HAAoX0teUdg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.18.tgz", + "integrity": "sha512-zsu47DgU0FQzSwi6sU9dZoEdUv7pc1AptSEz/Z8HBg54sV0Pbs3N0+CrIbTsgiu6EyoaNN9CHboqbLaz9lhOyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.18.tgz", + "integrity": "sha512-7H+3yqGgmnlDTRRhw/xpYY9J1kf4GC681nVc4GqKhExZTDrVVrV2tsOR9kso0fvgBdcTCcQShx4SLLoHgaLwhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.7", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", + "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "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/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", + "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-rc.7" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", + "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", + "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.5", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", + "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", + "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.5", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", + "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "@vitest/utils": "4.1.5", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", + "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", + "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "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/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "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/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "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/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.1.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "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/nwsapi": { + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/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": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "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/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.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/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "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==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.18.tgz", + "integrity": "sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.128.0", + "@rolldown/pluginutils": "1.0.0-rc.18" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.18", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.18", + "@rolldown/binding-darwin-x64": "1.0.0-rc.18", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.18", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.18", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.18", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.18", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.18", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.18", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.18", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.18" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.18.tgz", + "integrity": "sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "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/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.11.tgz", + "integrity": "sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.14", + "rolldown": "1.0.0-rc.18", + "tinyglobby": "^0.2.16" + }, + "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", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "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/vitest": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", + "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.5", + "@vitest/mocker": "4.1.5", + "@vitest/pretty-format": "4.1.5", + "@vitest/runner": "4.1.5", + "@vitest/snapshot": "4.1.5", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.5", + "@vitest/browser-preview": "4.1.5", + "@vitest/browser-webdriverio": "4.1.5", + "@vitest/coverage-istanbul": "4.1.5", + "@vitest/coverage-v8": "4.1.5", + "@vitest/ui": "4.1.5", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f12c240 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "league-of-legends-gui-overhaul", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc --noEmit && vite build", + "preview": "vite preview", + "test": "vitest run" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.30.1" + }, + "devDependencies": { + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.1.0", + "@testing-library/user-event": "^14.5.2", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^6.0.1", + "jsdom": "^25.0.1", + "typescript": "^5.6.3", + "vite": "^8.0.11", + "vitest": "^4.1.5" + } +} diff --git a/src/app/App.test.tsx b/src/app/App.test.tsx new file mode 100644 index 0000000..1de82c5 --- /dev/null +++ b/src/app/App.test.tsx @@ -0,0 +1,78 @@ +import { screen, within } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it } from "vitest"; +import { renderAppAt } from "../test/renderWithRouter"; + +describe("App shell", () => { + it("renders the main layout and navigation", () => { + renderAppAt("/"); + + const navigation = screen.getByRole("navigation", { name: /primary navigation/i }); + + expect(screen.getByText("Nexus Overhaul")).toBeInTheDocument(); + expect(navigation).toBeInTheDocument(); + expect(within(navigation).getByRole("link", { name: /home/i })).toBeInTheDocument(); + expect(within(navigation).getByRole("link", { name: /play/i })).toBeInTheDocument(); + expect(within(navigation).getByRole("link", { name: /champions/i })).toBeInTheDocument(); + expect(within(navigation).getByRole("link", { name: /profile/i })).toBeInTheDocument(); + expect(within(navigation).getByRole("link", { name: /settings/i })).toBeInTheDocument(); + expect(screen.getByRole("complementary", { name: /friends and social status/i })).toBeInTheDocument(); + expect(screen.getByText(/Status: local prototype/i)).toBeInTheDocument(); + expect(screen.getByText(/Theme: dusk-gold/i)).toBeInTheDocument(); + }); + + it.each([ + ["/", "Dashboard"], + ["/play", "Queue prototype"], + ["/champions", "Collection prototype"], + ["/champions/nyra", "Combat Profile"], + ["/profile", "Player profile"], + ["/settings", "Configuration prototype"] + ])("renders route %s", (path, expectedText) => { + renderAppAt(path); + + expect(screen.getByText(expectedText)).toBeInTheDocument(); + }); + + it("switches views through sidebar navigation", async () => { + const user = userEvent.setup(); + renderAppAt("/"); + const navigation = screen.getByRole("navigation", { name: /primary navigation/i }); + + await user.click(within(navigation).getByRole("link", { name: /champions/i })); + + expect(screen.getByRole("heading", { name: "Champions" })).toBeInTheDocument(); + }); + + it("opens a champion detail page from the champion grid", async () => { + const user = userEvent.setup(); + renderAppAt("/champions"); + + await user.click(screen.getByRole("link", { name: /nyra/i })); + + expect(screen.getByRole("heading", { name: "Nyra" })).toBeInTheDocument(); + expect(screen.getByText("Combat Profile")).toBeInTheDocument(); + }); + + it("applies a theme preset from settings", async () => { + const user = userEvent.setup(); + const { container } = renderAppAt("/settings"); + + await user.selectOptions(screen.getByLabelText(/theme preset/i), "arcane-teal"); + + expect(container.querySelector(".app-shell")).toHaveAttribute("data-theme", "arcane-teal"); + expect(screen.getByText(/Theme: arcane-teal/i)).toBeInTheDocument(); + expect(screen.getByText("Theme preview applied")).toBeInTheDocument(); + }); + + it("shows a toast when sending a social invite", async () => { + const user = userEvent.setup(); + renderAppAt("/"); + const socialSidebar = screen.getByRole("complementary", { name: /friends and social status/i }); + + await user.click(within(socialSidebar).getAllByRole("button", { name: /invite/i })[0]); + + expect(screen.getByText("Invite sent")).toBeInTheDocument(); + expect(screen.getByText(/received a prototype party invite/i)).toBeInTheDocument(); + }); +}); diff --git a/src/app/App.tsx b/src/app/App.tsx new file mode 100644 index 0000000..2cd82fc --- /dev/null +++ b/src/app/App.tsx @@ -0,0 +1,16 @@ +import { BrowserRouter } from "react-router-dom"; +import { AppProvider } from "./AppContext"; +import { AppRoutes } from "./router"; +import { AppShell } from "../layouts/AppShell"; + +export function App() { + return ( + + + + + + + + ); +} diff --git a/src/app/AppContext.tsx b/src/app/AppContext.tsx new file mode 100644 index 0000000..e868f9c --- /dev/null +++ b/src/app/AppContext.tsx @@ -0,0 +1,64 @@ +import { createContext, useContext, useMemo, useState } from "react"; +import type { ReactNode } from "react"; +import type { ToastVariant } from "../components/ui"; + +export type ThemePreset = "dusk-gold" | "arcane-teal" | "obsidian"; + +export type AppNotification = { + id: string; + title: string; + message: string; + variant?: ToastVariant; +}; + +type AppContextValue = { + theme: ThemePreset; + setTheme: (theme: ThemePreset) => void; + notifications: AppNotification[]; + notify: (notification: Omit) => void; + dismissNotification: (id: string) => void; +}; + +const AppContext = createContext(undefined); + +type AppProviderProps = { + children: ReactNode; +}; + +export function AppProvider({ children }: AppProviderProps) { + const [theme, setTheme] = useState("dusk-gold"); + const [notifications, setNotifications] = useState([]); + + const value = useMemo( + () => ({ + theme, + setTheme, + notifications, + notify: (notification) => { + setNotifications((current) => [ + ...current.slice(-2), + { + ...notification, + id: crypto.randomUUID() + } + ]); + }, + dismissNotification: (id) => { + setNotifications((current) => current.filter((notification) => notification.id !== id)); + } + }), + [notifications, theme] + ); + + return {children}; +} + +export function useAppContext() { + const context = useContext(AppContext); + + if (!context) { + throw new Error("useAppContext must be used inside AppProvider"); + } + + return context; +} diff --git a/src/app/router.tsx b/src/app/router.tsx new file mode 100644 index 0000000..f5396d1 --- /dev/null +++ b/src/app/router.tsx @@ -0,0 +1,13 @@ +import { Navigate, Route, Routes } from "react-router-dom"; +import { appRoutes } from "../config/routes"; + +export function AppRoutes() { + return ( + + {appRoutes.map((route) => ( + } /> + ))} + } /> + + ); +} diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx new file mode 100644 index 0000000..0e33af9 --- /dev/null +++ b/src/components/ui/Button.tsx @@ -0,0 +1,30 @@ +import type { ButtonHTMLAttributes, ReactNode } from "react"; + +type ButtonVariant = "primary" | "secondary" | "ghost"; +type ButtonSize = "sm" | "md" | "lg"; + +type ButtonProps = ButtonHTMLAttributes & { + children: ReactNode; + variant?: ButtonVariant; + size?: ButtonSize; + active?: boolean; +}; + +export function Button({ + children, + variant = "primary", + size = "md", + active = false, + className = "", + ...props +}: ButtonProps) { + const classes = ["ui-button", `ui-button-${variant}`, `ui-button-${size}`, active ? "is-active" : "", className] + .filter(Boolean) + .join(" "); + + return ( + + ); +} diff --git a/src/components/ui/Dropdown.tsx b/src/components/ui/Dropdown.tsx new file mode 100644 index 0000000..892c2d4 --- /dev/null +++ b/src/components/ui/Dropdown.tsx @@ -0,0 +1,26 @@ +import type { SelectHTMLAttributes } from "react"; + +export type DropdownOption = { + label: string; + value: string; +}; + +type DropdownProps = SelectHTMLAttributes & { + label: string; + options: DropdownOption[]; +}; + +export function Dropdown({ label, options, className = "", ...props }: DropdownProps) { + return ( + + ); +} diff --git a/src/components/ui/Modal.tsx b/src/components/ui/Modal.tsx new file mode 100644 index 0000000..9262ba3 --- /dev/null +++ b/src/components/ui/Modal.tsx @@ -0,0 +1,29 @@ +import type { ReactNode } from "react"; +import { Button } from "./Button"; + +type ModalProps = { + open: boolean; + title: string; + children: ReactNode; + onClose: () => void; +}; + +export function Modal({ open, title, children, onClose }: ModalProps) { + if (!open) { + return null; + } + + return ( +
+
+
+ + +
+
{children}
+
+
+ ); +} diff --git a/src/components/ui/Panel.tsx b/src/components/ui/Panel.tsx new file mode 100644 index 0000000..b834b93 --- /dev/null +++ b/src/components/ui/Panel.tsx @@ -0,0 +1,21 @@ +import type { HTMLAttributes, ReactNode } from "react"; + +type PanelProps = HTMLAttributes & { + children: ReactNode; + title?: string; + subtitle?: string; +}; + +export function Panel({ children, title, subtitle, className = "", ...props }: PanelProps) { + return ( +
+ {(title || subtitle) && ( +
+ {title &&

{title}

} + {subtitle &&

{subtitle}

} +
+ )} + {children} +
+ ); +} diff --git a/src/components/ui/SearchInput.tsx b/src/components/ui/SearchInput.tsx new file mode 100644 index 0000000..9e281de --- /dev/null +++ b/src/components/ui/SearchInput.tsx @@ -0,0 +1,14 @@ +import type { InputHTMLAttributes } from "react"; + +type SearchInputProps = InputHTMLAttributes & { + label?: string; +}; + +export function SearchInput({ label = "Search", className = "", ...props }: SearchInputProps) { + return ( + + ); +} diff --git a/src/components/ui/Tabs.tsx b/src/components/ui/Tabs.tsx new file mode 100644 index 0000000..6e8e337 --- /dev/null +++ b/src/components/ui/Tabs.tsx @@ -0,0 +1,42 @@ +import { useState } from "react"; +import type { ReactNode } from "react"; +import { Button } from "./Button"; + +export type TabItem = { + id: string; + label: string; + content: ReactNode; +}; + +type TabsProps = { + tabs: TabItem[]; + defaultTabId?: string; +}; + +export function Tabs({ tabs, defaultTabId }: TabsProps) { + const [activeId, setActiveId] = useState(defaultTabId ?? tabs[0]?.id); + const activeTab = tabs.find((tab) => tab.id === activeId) ?? tabs[0]; + + return ( +
+
+ {tabs.map((tab) => ( + + ))} +
+
+ {activeTab.content} +
+
+ ); +} diff --git a/src/components/ui/Toast.tsx b/src/components/ui/Toast.tsx new file mode 100644 index 0000000..6c5e361 --- /dev/null +++ b/src/components/ui/Toast.tsx @@ -0,0 +1,24 @@ +export type ToastVariant = "info" | "success" | "warning"; + +type ToastProps = { + title: string; + message: string; + variant?: ToastVariant; + onDismiss?: () => void; +}; + +export function Toast({ title, message, variant = "info", onDismiss }: ToastProps) { + return ( + + ); +} diff --git a/src/components/ui/Tooltip.tsx b/src/components/ui/Tooltip.tsx new file mode 100644 index 0000000..406611e --- /dev/null +++ b/src/components/ui/Tooltip.tsx @@ -0,0 +1,14 @@ +import type { ReactNode } from "react"; + +type TooltipProps = { + content: string; + children: ReactNode; +}; + +export function Tooltip({ content, children }: TooltipProps) { + return ( + + {children} + + ); +} diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts new file mode 100644 index 0000000..d84cffa --- /dev/null +++ b/src/components/ui/index.ts @@ -0,0 +1,11 @@ +export { Button } from "./Button"; +export { Dropdown } from "./Dropdown"; +export type { DropdownOption } from "./Dropdown"; +export { Modal } from "./Modal"; +export { Panel } from "./Panel"; +export { SearchInput } from "./SearchInput"; +export { Tabs } from "./Tabs"; +export type { TabItem } from "./Tabs"; +export { Toast } from "./Toast"; +export type { ToastVariant } from "./Toast"; +export { Tooltip } from "./Tooltip"; diff --git a/src/components/ui/ui.test.tsx b/src/components/ui/ui.test.tsx new file mode 100644 index 0000000..73b2f72 --- /dev/null +++ b/src/components/ui/ui.test.tsx @@ -0,0 +1,44 @@ +import { screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { Button, Dropdown, Panel, SearchInput } from "."; +import { renderWithRouter } from "../../test/renderWithRouter"; + +describe("base UI components", () => { + it("renders a button", () => { + renderWithRouter(); + + expect(screen.getByRole("button", { name: /confirm/i })).toBeInTheDocument(); + }); + + it("renders a panel with title and content", () => { + renderWithRouter( + +

Panel body

+
+ ); + + expect(screen.getByRole("heading", { name: /panel title/i })).toBeInTheDocument(); + expect(screen.getByText("Panel body")).toBeInTheDocument(); + }); + + it("renders a search input", () => { + renderWithRouter(); + + expect(screen.getByLabelText(/champion search/i)).toBeInTheDocument(); + }); + + it("renders a dropdown", () => { + renderWithRouter( + + ); + + expect(screen.getByLabelText(/role/i)).toBeInTheDocument(); + expect(screen.getByRole("option", { name: /duelist/i })).toBeInTheDocument(); + }); +}); diff --git a/src/config/navigation.ts b/src/config/navigation.ts new file mode 100644 index 0000000..f141951 --- /dev/null +++ b/src/config/navigation.ts @@ -0,0 +1,44 @@ +import type { NavigationItem } from "../types/navigation"; + +export const navigationItems: NavigationItem[] = [ + { + id: "home", + label: "Home", + path: "/", + iconName: "H", + section: "primary", + enabled: true + }, + { + id: "play", + label: "Play", + path: "/play", + iconName: "P", + section: "primary", + enabled: true + }, + { + id: "champions", + label: "Champions", + path: "/champions", + iconName: "C", + section: "collection", + enabled: true + }, + { + id: "profile", + label: "Profile", + path: "/profile", + iconName: "R", + section: "account", + enabled: true + }, + { + id: "settings", + label: "Settings", + path: "/settings", + iconName: "S", + section: "system", + enabled: true + } +]; diff --git a/src/config/routes.tsx b/src/config/routes.tsx new file mode 100644 index 0000000..a7abcc7 --- /dev/null +++ b/src/config/routes.tsx @@ -0,0 +1,49 @@ +import type { ComponentType } from "react"; +import { ChampionDetailPage } from "../pages/ChampionDetailPage"; +import { ChampionsPage } from "../pages/ChampionsPage"; +import { HomePage } from "../pages/HomePage"; +import { PlayPage } from "../pages/PlayPage"; +import { ProfilePage } from "../pages/ProfilePage"; +import { SettingsPage } from "../pages/SettingsPage"; +import type { AppRoute } from "../types/navigation"; + +type RouteComponent = ComponentType; + +export const appRoutes: Array = [ + { + id: "home", + title: "Home", + path: "/", + component: HomePage + }, + { + id: "play", + title: "Play", + path: "/play", + component: PlayPage + }, + { + id: "champions", + title: "Champions", + path: "/champions", + component: ChampionsPage + }, + { + id: "champion-detail", + title: "Champion Detail", + path: "/champions/:championId", + component: ChampionDetailPage + }, + { + id: "profile", + title: "Profile", + path: "/profile", + component: ProfilePage + }, + { + id: "settings", + title: "Settings", + path: "/settings", + component: SettingsPage + } +]; diff --git a/src/data/champions.mock.ts b/src/data/champions.mock.ts new file mode 100644 index 0000000..d974861 --- /dev/null +++ b/src/data/champions.mock.ts @@ -0,0 +1,40 @@ +import type { Champion } from "../types/domain"; + +export const champions: Champion[] = [ + { + id: "nyra", + name: "Nyra", + role: "Invoker", + difficulty: "Medium", + tags: ["arcane", "control", "mid"], + shortDescription: "A stormbound tactician who shapes fights with precise zones.", + accentColor: "#36d7d0" + }, + { + id: "kael", + name: "Kael", + role: "Duelist", + difficulty: "High", + tags: ["melee", "burst", "solo"], + shortDescription: "A blade-focused skirmisher built around timing and pressure.", + accentColor: "#c89b3c" + }, + { + id: "maera", + name: "Maera", + role: "Guardian", + difficulty: "Low", + tags: ["support", "shield", "teamfight"], + shortDescription: "A steady frontliner who protects allies and anchors engages.", + accentColor: "#5b8cff" + }, + { + id: "oren", + name: "Oren", + role: "Striker", + difficulty: "Medium", + tags: ["ranged", "tempo", "objective"], + shortDescription: "A marksman prototype focused on objective windows and spacing.", + accentColor: "#e56b6f" + } +]; diff --git a/src/data/friends.mock.ts b/src/data/friends.mock.ts new file mode 100644 index 0000000..5863756 --- /dev/null +++ b/src/data/friends.mock.ts @@ -0,0 +1,25 @@ +import type { Friend } from "../types/domain"; + +export const friends: Friend[] = [ + { + id: "friend-1", + displayName: "Astra", + status: "online", + activity: "Browsing champions", + level: 42 + }, + { + id: "friend-2", + displayName: "Vey", + status: "in-game", + activity: "Draft lobby", + level: 57 + }, + { + id: "friend-3", + displayName: "Solin", + status: "away", + activity: "Idle", + level: 31 + } +]; diff --git a/src/data/matchHistory.mock.ts b/src/data/matchHistory.mock.ts new file mode 100644 index 0000000..8806ddc --- /dev/null +++ b/src/data/matchHistory.mock.ts @@ -0,0 +1,22 @@ +import type { MatchHistoryEntry } from "../types/domain"; + +export const matchHistory: MatchHistoryEntry[] = [ + { + id: "match-1", + championName: "Nyra", + mode: "Summoner Rift", + result: "Victory", + kda: "8 / 2 / 11", + duration: "31m 12s", + date: "Today" + }, + { + id: "match-2", + championName: "Maera", + mode: "ARAM", + result: "Defeat", + kda: "2 / 6 / 19", + duration: "18m 44s", + date: "Yesterday" + } +]; diff --git a/src/data/news.mock.ts b/src/data/news.mock.ts new file mode 100644 index 0000000..017614f --- /dev/null +++ b/src/data/news.mock.ts @@ -0,0 +1,18 @@ +import type { NewsItem } from "../types/domain"; + +export const newsItems: NewsItem[] = [ + { + id: "news-1", + title: "Base Shell Prototype Ready", + category: "Development", + date: "2026-05-10", + summary: "The first interface foundation focuses on layout, routing, reusable UI, and mock data." + }, + { + id: "news-2", + title: "Champion Grid Planning", + category: "Collection", + date: "2026-05-10", + summary: "The champion overview starts with searchable mock cards and can later gain filters." + } +]; diff --git a/src/features/champions/championLookup.ts b/src/features/champions/championLookup.ts new file mode 100644 index 0000000..5c01b0f --- /dev/null +++ b/src/features/champions/championLookup.ts @@ -0,0 +1,9 @@ +import { champions } from "../../data/champions.mock"; + +export function getChampionById(championId: string | undefined) { + if (!championId) { + return undefined; + } + + return champions.find((champion) => champion.id === championId); +} diff --git a/src/features/champions/filterChampions.ts b/src/features/champions/filterChampions.ts new file mode 100644 index 0000000..20e8dc4 --- /dev/null +++ b/src/features/champions/filterChampions.ts @@ -0,0 +1,15 @@ +import type { Champion } from "../../types/domain"; + +export function filterChampions(champions: Champion[], query: string, role: string): Champion[] { + const normalizedQuery = query.trim().toLowerCase(); + + return champions.filter((champion) => { + const matchesRole = role === "all" || champion.role === role; + const matchesQuery = + normalizedQuery.length === 0 || + champion.name.toLowerCase().includes(normalizedQuery) || + champion.tags.some((tag) => tag.includes(normalizedQuery)); + + return matchesRole && matchesQuery; + }); +} diff --git a/src/features/profile/profileSummary.ts b/src/features/profile/profileSummary.ts new file mode 100644 index 0000000..a8fbec9 --- /dev/null +++ b/src/features/profile/profileSummary.ts @@ -0,0 +1,6 @@ +export const profileSummary = { + displayName: "Toxic", + level: 42, + rank: "Gold prototype rank", + note: "No persistence in iteration 1." +}; diff --git a/src/features/social/SocialSidebar.tsx b/src/features/social/SocialSidebar.tsx new file mode 100644 index 0000000..0f2d51a --- /dev/null +++ b/src/features/social/SocialSidebar.tsx @@ -0,0 +1,44 @@ +import { useAppContext } from "../../app/AppContext"; +import { Button } from "../../components/ui"; +import { friends } from "../../data/friends.mock"; +import { getFriendStatusLabel } from "./friendStatus"; + +export function SocialSidebar() { + const { notify } = useAppContext(); + + return ( + + ); +} diff --git a/src/features/social/friendStatus.ts b/src/features/social/friendStatus.ts new file mode 100644 index 0000000..925c477 --- /dev/null +++ b/src/features/social/friendStatus.ts @@ -0,0 +1,12 @@ +import type { FriendStatus } from "../../types/domain"; + +export function getFriendStatusLabel(status: FriendStatus): string { + const labels: Record = { + online: "Online", + away: "Away", + "in-game": "In game", + offline: "Offline" + }; + + return labels[status]; +} diff --git a/src/layouts/AppShell.tsx b/src/layouts/AppShell.tsx new file mode 100644 index 0000000..f15c197 --- /dev/null +++ b/src/layouts/AppShell.tsx @@ -0,0 +1,106 @@ +import type { ReactNode } from "react"; +import { NavLink } from "react-router-dom"; +import { useAppContext } from "../app/AppContext"; +import { navigationItems } from "../config/navigation"; +import { Button } from "../components/ui/Button"; +import { Toast } from "../components/ui/Toast"; +import { SocialSidebar } from "../features/social/SocialSidebar"; + +type AppShellProps = { + children: ReactNode; +}; + +export function AppShell({ children }: AppShellProps) { + const enabledItems = navigationItems.filter((item) => item.enabled !== false); + const { dismissNotification, notifications, notify, theme } = useAppContext(); + + return ( +
+ + +
+
+
+ Iteration 1 +

MOBA Interface Foundation

+
+
+ + +
+
+ +
{children}
+ +
+ Status: local prototype + Theme: {theme} + Modules: base shell +
+
+ + + + {notifications.length > 0 && ( +
+ {notifications.map((notification) => ( + dismissNotification(notification.id)} + /> + ))} +
+ )} +
+ ); +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..3ece158 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import { App } from "./app/App"; +import "./styles/theme.css"; +import "./styles/global.css"; + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + + + +); diff --git a/src/pages/ChampionDetailPage.tsx b/src/pages/ChampionDetailPage.tsx new file mode 100644 index 0000000..cc51e6f --- /dev/null +++ b/src/pages/ChampionDetailPage.tsx @@ -0,0 +1,67 @@ +import type { CSSProperties } from "react"; +import { Link, useParams } from "react-router-dom"; +import { Panel } from "../components/ui"; +import { getChampionById } from "../features/champions/championLookup"; + +export function ChampionDetailPage() { + const { championId } = useParams(); + const champion = getChampionById(championId); + + if (!champion) { + return ( +
+ + + Return to champions + + +
+ ); + } + + return ( +
+
+
+ {champion.name.charAt(0)} +
+
+ {champion.role} +

{champion.name}

+

{champion.shortDescription}

+
+ {champion.tags.map((tag) => ( + {tag} + ))} +
+
+
+ +
+ +
+
+
Difficulty
+
{champion.difficulty}
+
+
+
Primary Role
+
{champion.role}
+
+
+
Theme Hint
+
{champion.accentColor ?? "Default accent"}
+
+
+
+ + +

Abilities, skins, progression, lore, and build recommendations can be added as independent modules.

+ + Back to overview + +
+
+
+ ); +} diff --git a/src/pages/ChampionsPage.tsx b/src/pages/ChampionsPage.tsx new file mode 100644 index 0000000..1afc918 --- /dev/null +++ b/src/pages/ChampionsPage.tsx @@ -0,0 +1,72 @@ +import type { CSSProperties } from "react"; +import { useMemo, useState } from "react"; +import { Link } from "react-router-dom"; +import { Dropdown, Panel, SearchInput } from "../components/ui"; +import { champions } from "../data/champions.mock"; +import { filterChampions } from "../features/champions/filterChampions"; + +const roleOptions = [ + { label: "All roles", value: "all" }, + { label: "Controller", value: "Controller" }, + { label: "Duelist", value: "Duelist" }, + { label: "Guardian", value: "Guardian" }, + { label: "Invoker", value: "Invoker" }, + { label: "Striker", value: "Striker" } +]; + +export function ChampionsPage() { + const [query, setQuery] = useState(""); + const [role, setRole] = useState("all"); + + const filteredChampions = useMemo(() => { + return filterChampions(champions, query, role); + }, [query, role]); + + return ( +
+
+ Collection prototype +

Champions

+

Searchable mock champion cards with filtering hooks for later collection features.

+
+ +
+ setQuery(event.target.value)} + /> + setRole(event.target.value)} /> +
+ +
+ {filteredChampions.map((champion) => ( + +
+ {champion.name.charAt(0)} +
+
+
+
+ {champion.role} +

{champion.name}

+
+ + {champion.difficulty} + +
+

{champion.shortDescription}

+
+ {champion.tags.map((tag) => ( + {tag} + ))} +
+ View details +
+ + ))} +
+
+ ); +} diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx new file mode 100644 index 0000000..6619fb3 --- /dev/null +++ b/src/pages/HomePage.tsx @@ -0,0 +1,68 @@ +import { Link } from "react-router-dom"; +import { newsItems } from "../data/news.mock"; +import { Button, Panel, Tabs, Toast } from "../components/ui"; + +export function HomePage() { + return ( +
+
+
+ Dashboard +

Build the foundation before the spectacle.

+

+ A modular shell for future MOBA-inspired views, themes, social panels, and champion experiences. +

+
+
+ + Play Prototype + + + Browse champions + +
+
+ +
+ +
+ + + +
+
+ + +
+ {newsItems.map((item) => ( +
+ {item.category} +

{item.title}

+

{item.summary}

+
+ ))} +
+
+ + + Shell, navigation, status bar, and content regions are wired.

+ }, + { + id: "data", + label: "Data", + content:

Mock data is isolated from page and UI components.

+ } + ]} + /> +
+ + +
+
+ ); +} diff --git a/src/pages/PlayPage.tsx b/src/pages/PlayPage.tsx new file mode 100644 index 0000000..3a5a9ed --- /dev/null +++ b/src/pages/PlayPage.tsx @@ -0,0 +1,49 @@ +import { useState } from "react"; +import { Button, Modal, Panel, Tooltip } from "../components/ui"; + +const playModes = [ + { + id: "rift", + name: "Summoner Rift", + description: "Structured 5v5 queue placeholder for the main arena experience." + }, + { + id: "aram", + name: "ARAM", + description: "Compact teamfight mode placeholder for faster sessions." + }, + { + id: "custom", + name: "Custom", + description: "Private lobby placeholder for future party and settings flows." + } +]; + +export function PlayPage() { + const [modalOpen, setModalOpen] = useState(false); + + return ( +
+
+ Queue prototype +

Play

+

Select a mock mode and open the lobby dialog. No matchmaking exists in iteration 1.

+
+ +
+ {playModes.map((mode) => ( + +

{mode.description}

+ + + +
+ ))} +
+ + setModalOpen(false)}> +

This dialog proves that modal composition is available for later queue and party flows.

+
+
+ ); +} diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx new file mode 100644 index 0000000..a1e2c7a --- /dev/null +++ b/src/pages/ProfilePage.tsx @@ -0,0 +1,59 @@ +import { Panel } from "../components/ui"; +import { friends } from "../data/friends.mock"; +import { matchHistory } from "../data/matchHistory.mock"; +import { profileSummary } from "../features/profile/profileSummary"; +import { getFriendStatusLabel } from "../features/social/friendStatus"; + +export function ProfilePage() { + return ( +
+
+ +
+ Player profile +

{profileSummary.displayName}

+

+ Level {profileSummary.level} - {profileSummary.rank} - {profileSummary.note} +

+
+
+ +
+ +
+ {matchHistory.map((match) => ( +
+
+

{match.championName}

+

+ {match.mode} - {match.duration} - {match.date} +

+
+ {match.result} + {match.kda} +
+ ))} +
+
+ + +
+ {friends.map((friend) => ( +
+ +
+

{friend.displayName}

+

+ {getFriendStatusLabel(friend.status)} - {friend.activity} {friend.level ? `- Level ${friend.level}` : ""} +

+
+
+ ))} +
+
+
+
+ ); +} diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx new file mode 100644 index 0000000..19fb02c --- /dev/null +++ b/src/pages/SettingsPage.tsx @@ -0,0 +1,76 @@ +import type { ThemePreset } from "../app/AppContext"; +import { useAppContext } from "../app/AppContext"; +import { Button, Dropdown, Panel, SearchInput } from "../components/ui"; + +const densityOptions = [ + { label: "Comfortable", value: "comfortable" }, + { label: "Compact", value: "compact" }, + { label: "Dense", value: "dense" } +]; + +const themeOptions = [ + { label: "Dusk Gold", value: "dusk-gold" }, + { label: "Arcane Teal", value: "arcane-teal" }, + { label: "Obsidian", value: "obsidian" } +]; + +export function SettingsPage() { + const { notify, setTheme, theme } = useAppContext(); + + return ( +
+
+ Configuration prototype +

Settings

+

Demonstrates form controls for future theme and interface preferences.

+
+ + +
+ { + const nextTheme = event.target.value as ThemePreset; + setTheme(nextTheme); + notify({ + title: "Theme preview applied", + message: `${nextTheme} is active for this session.`, + variant: "success" + }); + }} + /> + + +
+
+ + +
+
+
+ ); +} diff --git a/src/styles/global.css b/src/styles/global.css new file mode 100644 index 0000000..a47b006 --- /dev/null +++ b/src/styles/global.css @@ -0,0 +1,681 @@ +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-width: 320px; + min-height: 100vh; + background: + radial-gradient(circle at top left, rgb(54 215 208 / 0.14), transparent 28rem), + linear-gradient(135deg, #05070d 0%, var(--color-bg) 46%, #0b1220 100%); + color: var(--color-text); + font-family: var(--font-body); +} + +button, +input, +select { + font: inherit; +} + +a { + color: inherit; + text-decoration: none; +} + +h1, +h2, +h3, +p { + margin: 0; +} + +p { + color: var(--color-text-muted); + line-height: 1.6; +} + +.app-shell { + display: grid; + grid-template-columns: 17rem minmax(0, 1fr) 18rem; + min-height: 100vh; +} + +.sidebar { + border-right: 1px solid var(--color-border); + background: rgb(7 10 18 / 0.92); + padding: var(--space-5); +} + +.social-sidebar { + border-left: 1px solid var(--color-border); + background: linear-gradient(180deg, rgb(13 20 34 / 0.94), rgb(7 10 18 / 0.92)); + padding: var(--space-5); +} + +.social-sidebar h2 { + font-family: var(--font-display); + font-size: 1.2rem; +} + +.social-list { + display: grid; + gap: var(--space-3); + margin-top: var(--space-4); +} + +.social-card { + display: grid; + grid-template-columns: auto minmax(0, 1fr) auto; + gap: var(--space-3); + align-items: center; + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + background: rgb(17 27 45 / 0.76); + padding: var(--space-3); +} + +.social-card h3 { + margin-bottom: var(--space-1); + font-size: 0.95rem; +} + +.social-card span:not(.status-dot) { + color: var(--color-text-muted); + font-size: 0.8rem; +} + +.brand { + display: flex; + gap: var(--space-3); + align-items: center; + margin-bottom: var(--space-6); +} + +.brand span:last-child { + display: block; + color: var(--color-text-muted); + font-size: 0.82rem; +} + +.brand-mark, +.nav-icon, +.profile-avatar { + display: inline-grid; + place-items: center; + border: 1px solid var(--color-border-strong); + background: linear-gradient(135deg, rgb(200 155 60 / 0.28), rgb(54 215 208 / 0.1)); + color: var(--color-gold-bright); + font-weight: 700; +} + +.brand-mark { + width: 2.5rem; + height: 2.5rem; + border-radius: var(--radius-md); +} + +.nav-list { + display: grid; + gap: var(--space-2); +} + +.nav-item { + display: flex; + align-items: center; + gap: var(--space-3); + min-height: 2.75rem; + border: 1px solid transparent; + border-radius: var(--radius-md); + padding: 0 var(--space-3); + color: var(--color-text-muted); + transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast); +} + +.nav-item:hover, +.nav-item-active { + border-color: var(--color-border-strong); + background: rgb(200 155 60 / 0.1); + color: var(--color-text); +} + +.nav-icon { + width: 1.75rem; + height: 1.75rem; + border-radius: var(--radius-sm); + font-size: 0.8rem; +} + +.app-frame { + display: grid; + grid-template-rows: auto 1fr auto; + min-width: 0; +} + +.topbar, +.status-bar { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-4); + border-bottom: 1px solid var(--color-border); + padding: var(--space-4) var(--space-5); + background: rgb(13 20 34 / 0.82); +} + +.topbar h1 { + font-family: var(--font-display); + font-size: 1.35rem; +} + +.topbar-actions, +.button-row, +.hero-actions { + display: flex; + flex-wrap: wrap; + gap: var(--space-3); + align-items: center; +} + +.content-area { + min-width: 0; + padding: var(--space-5); +} + +.status-bar { + border-top: 1px solid var(--color-border); + border-bottom: 0; + color: var(--color-text-muted); + font-size: 0.85rem; +} + +.page-stack { + display: grid; + gap: var(--space-5); +} + +.page-heading, +.hero-panel, +.profile-header { + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + background: linear-gradient(135deg, rgb(22 36 58 / 0.95), rgb(13 20 34 / 0.88)); + box-shadow: var(--shadow-panel); + padding: var(--space-5); +} + +.hero-panel { + display: flex; + justify-content: space-between; + gap: var(--space-5); +} + +.hero-panel h2, +.page-heading h2, +.profile-header h2 { + margin-bottom: var(--space-2); + font-family: var(--font-display); + font-size: 2rem; +} + +.eyebrow { + display: inline-block; + margin-bottom: var(--space-2); + color: var(--color-gold-bright); + font-size: 0.74rem; + font-weight: 700; + text-transform: uppercase; +} + +.dashboard-grid, +.mode-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: var(--space-4); +} + +.champion-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); + gap: var(--space-4); +} + +.ui-panel, +.champion-card, +.compact-card, +.ui-toast { + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + background: rgb(17 27 45 / 0.9); + box-shadow: var(--shadow-panel); + padding: var(--space-4); +} + +.ui-panel-header { + margin-bottom: var(--space-4); +} + +.ui-panel-header h2 { + margin-bottom: var(--space-1); + font-size: 1.1rem; +} + +.ui-button, +.action-link { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 2.5rem; + border: 1px solid var(--color-border-strong); + border-radius: var(--radius-sm); + padding: 0 var(--space-4); + color: var(--color-text); + cursor: pointer; + transition: transform var(--transition-fast), border-color var(--transition-fast), background var(--transition-fast); +} + +.ui-button:hover, +.action-link:hover { + transform: translateY(-1px); + border-color: var(--color-gold-bright); +} + +.ui-button-primary, +.action-link { + background: linear-gradient(135deg, #9b752b, #312613); +} + +.ui-button-secondary { + background: rgb(54 215 208 / 0.12); + border-color: rgb(54 215 208 / 0.55); +} + +.ui-button-ghost { + background: transparent; + border-color: var(--color-border); + color: var(--color-text-muted); +} + +.ui-button-sm { + min-height: 2rem; + padding: 0 var(--space-3); + font-size: 0.86rem; +} + +.ui-button-lg { + min-height: 3rem; +} + +.ui-button:disabled { + cursor: not-allowed; + opacity: 0.55; +} + +.ui-button.is-active { + border-color: var(--color-teal); + color: var(--color-text); +} + +.ui-field { + display: grid; + gap: var(--space-2); + color: var(--color-text-muted); +} + +.ui-input, +.ui-select { + width: 100%; + min-height: 2.75rem; + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); + background: var(--color-bg-elevated); + color: var(--color-text); + padding: 0 var(--space-3); +} + +.toolbar, +.settings-grid { + display: grid; + grid-template-columns: minmax(0, 1fr) 14rem; + gap: var(--space-4); +} + +.settings-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin-bottom: var(--space-4); +} + +.list-stack { + display: grid; + gap: var(--space-3); +} + +.compact-card h3, +.friend-row h3, +.champion-card h3 { + margin-bottom: var(--space-1); +} + +.compact-card-row, +.friend-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-3); +} + +.champion-card { + display: grid; + gap: var(--space-4); + transition: border-color var(--transition-fast), box-shadow var(--transition-fast), transform var(--transition-fast); +} + +.champion-card:hover { + border-color: var(--color-gold); + box-shadow: var(--shadow-glow), var(--shadow-panel); + transform: translateY(-2px); +} + +.champion-card-header { + display: flex; + justify-content: space-between; + gap: var(--space-3); + align-items: flex-start; +} + +.difficulty-badge, +.card-action { + border: 1px solid var(--color-border); + border-radius: 999px; + padding: var(--space-1) var(--space-2); + color: var(--color-text-muted); + font-size: 0.78rem; + font-weight: 700; +} + +.difficulty-low { + border-color: var(--color-success); + color: var(--color-success); +} + +.difficulty-medium { + border-color: var(--color-warning); + color: var(--color-warning); +} + +.difficulty-high { + border-color: var(--color-danger); + color: var(--color-danger); +} + +.card-action { + display: inline-flex; + margin-top: var(--space-4); + border-color: var(--color-gold); + color: var(--color-gold-bright); +} + +.champion-sigil { + width: 4rem; + height: 4rem; + display: grid; + place-items: center; + border: 1px solid var(--champion-accent, var(--color-gold)); + border-radius: var(--radius-md); + background: color-mix(in srgb, var(--champion-accent, var(--color-gold)) 20%, transparent); + color: var(--color-text); + font-size: 1.5rem; + font-weight: 800; +} + +.tag-row { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); + margin-top: var(--space-3); +} + +.tag-row span { + border: 1px solid var(--color-border); + border-radius: 999px; + padding: var(--space-1) var(--space-2); + color: var(--color-text-muted); + font-size: 0.8rem; +} + +.profile-header { + display: flex; + align-items: center; + gap: var(--space-4); +} + +.champion-detail-hero { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: var(--space-5); + align-items: center; + border: 1px solid var(--color-border-strong); + border-radius: var(--radius-lg); + background: + radial-gradient(circle at top left, color-mix(in srgb, var(--champion-accent, var(--color-gold)) 24%, transparent), transparent 24rem), + linear-gradient(135deg, rgb(22 36 58 / 0.96), rgb(7 10 18 / 0.94)); + box-shadow: var(--shadow-glow), var(--shadow-panel); + padding: var(--space-6); +} + +.champion-detail-hero h2 { + margin-bottom: var(--space-2); + font-family: var(--font-display); + font-size: 2.5rem; +} + +.champion-portrait { + display: grid; + place-items: center; + width: 8rem; + height: 8rem; + border: 1px solid var(--champion-accent, var(--color-gold)); + border-radius: var(--radius-lg); + background: + linear-gradient(135deg, color-mix(in srgb, var(--champion-accent, var(--color-gold)) 28%, transparent), transparent), + var(--color-surface-strong); + color: var(--color-text); + font-family: var(--font-display); + font-size: 3rem; +} + +.stat-list { + display: grid; + gap: var(--space-3); +} + +.stat-list div { + display: flex; + justify-content: space-between; + gap: var(--space-4); + border-bottom: 1px solid var(--color-border); + padding-bottom: var(--space-2); +} + +.stat-list dt { + color: var(--color-text-muted); +} + +.stat-list dd { + margin: 0; + color: var(--color-text); +} + +.action-link-inline { + margin-top: var(--space-4); +} + +.profile-avatar { + width: 5rem; + height: 5rem; + border-radius: 50%; + font-size: 2rem; +} + +.status-dot { + width: 0.75rem; + height: 0.75rem; + flex: 0 0 auto; + border-radius: 50%; + background: var(--color-text-muted); +} + +.status-online, +.status-in-game { + background: var(--color-success); +} + +.status-away { + background: var(--color-warning); +} + +.result-win { + color: var(--color-success); +} + +.result-loss { + color: var(--color-danger); +} + +.text-link { + color: var(--color-teal); +} + +.ui-tabs { + display: grid; + gap: var(--space-3); +} + +.ui-tab-list { + display: flex; + gap: var(--space-2); +} + +.ui-tab-panel { + color: var(--color-text-muted); +} + +.ui-tooltip { + position: relative; + display: inline-flex; +} + +.ui-tooltip:hover::after { + position: absolute; + left: 0; + bottom: calc(100% + var(--space-2)); + z-index: 10; + min-width: 12rem; + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); + background: var(--color-bg-elevated); + color: var(--color-text); + content: attr(data-tooltip); + padding: var(--space-2); +} + +.ui-toast { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: var(--space-1); + border-color: rgb(54 215 208 / 0.45); +} + +.ui-toast div { + display: grid; + gap: var(--space-1); +} + +.toast-stack { + position: fixed; + right: var(--space-5); + bottom: var(--space-5); + z-index: 20; + display: grid; + width: min(24rem, calc(100vw - 2rem)); + gap: var(--space-3); +} + +.toast-dismiss { + border: 0; + background: transparent; + color: var(--color-text-muted); + cursor: pointer; + font-weight: 700; +} + +.ui-toast-success { + border-color: var(--color-success); +} + +.ui-toast-warning { + border-color: var(--color-warning); +} + +.ui-modal-backdrop { + position: fixed; + inset: 0; + display: grid; + place-items: center; + background: rgb(0 0 0 / 0.62); + padding: var(--space-5); +} + +.ui-modal { + width: min(34rem, 100%); + border: 1px solid var(--color-border-strong); + border-radius: var(--radius-lg); + background: var(--color-surface); + box-shadow: var(--shadow-glow), var(--shadow-panel); + padding: var(--space-5); +} + +.ui-modal-header { + display: flex; + justify-content: space-between; + gap: var(--space-4); + margin-bottom: var(--space-4); +} + +@media (max-width: 860px) { + .app-shell { + grid-template-columns: 1fr; + } + + .sidebar { + border-right: 0; + border-bottom: 1px solid var(--color-border); + } + + .social-sidebar { + border-top: 1px solid var(--color-border); + border-left: 0; + } + + .nav-list, + .dashboard-grid, + .mode-grid, + .toolbar, + .settings-grid { + grid-template-columns: 1fr; + } + + .hero-panel, + .champion-detail-hero, + .topbar, + .status-bar { + align-items: flex-start; + flex-direction: column; + } + + .champion-detail-hero { + grid-template-columns: 1fr; + } +} diff --git a/src/styles/theme.css b/src/styles/theme.css new file mode 100644 index 0000000..8a90ed4 --- /dev/null +++ b/src/styles/theme.css @@ -0,0 +1,60 @@ +:root { + color-scheme: dark; + --color-bg: #070a12; + --color-bg-elevated: #0d1422; + --color-surface: #111b2d; + --color-surface-strong: #16243a; + --color-border: #2b3a52; + --color-border-strong: #6f5622; + --color-text: #f2f0e8; + --color-text-muted: #aeb8c8; + --color-gold: #c89b3c; + --color-gold-bright: #f0c66c; + --color-teal: #36d7d0; + --color-blue: #5b8cff; + --color-danger: #e56b6f; + --color-warning: #f0b35f; + --color-success: #61d394; + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --space-5: 1.5rem; + --space-6: 2rem; + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + --shadow-panel: 0 18px 48px rgb(0 0 0 / 0.32); + --shadow-glow: 0 0 28px rgb(54 215 208 / 0.14); + --font-body: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --font-display: Georgia, "Times New Roman", serif; + --transition-fast: 160ms ease; +} + +[data-theme="arcane-teal"] { + --color-bg: #041216; + --color-bg-elevated: #092029; + --color-surface: #0c2a33; + --color-surface-strong: #123945; + --color-border: #1d5561; + --color-border-strong: #36d7d0; + --color-gold: #b7a46a; + --color-gold-bright: #ead58a; + --color-teal: #55f2e8; + --color-blue: #61a4ff; + --shadow-glow: 0 0 30px rgb(85 242 232 / 0.18); +} + +[data-theme="obsidian"] { + --color-bg: #050507; + --color-bg-elevated: #0d0d12; + --color-surface: #15151c; + --color-surface-strong: #20202a; + --color-border: #343444; + --color-border-strong: #8d6d2e; + --color-gold: #b98a32; + --color-gold-bright: #e4bc65; + --color-teal: #6796a7; + --color-blue: #7f8fbf; + --shadow-glow: 0 0 24px rgb(185 138 50 / 0.14); +} diff --git a/src/test/renderWithRouter.tsx b/src/test/renderWithRouter.tsx new file mode 100644 index 0000000..fe508f8 --- /dev/null +++ b/src/test/renderWithRouter.tsx @@ -0,0 +1,24 @@ +import type { ReactNode } from "react"; +import { MemoryRouter } from "react-router-dom"; +import { render } from "@testing-library/react"; +import { AppProvider } from "../app/AppContext"; +import { AppRoutes } from "../app/router"; +import { AppShell } from "../layouts/AppShell"; + +export function renderAppAt(path = "/") { + return render( + + + + + + + + ); +} + +export function renderWithRouter(children: ReactNode) { + return render( + {children} + ); +} diff --git a/src/test/setup.ts b/src/test/setup.ts new file mode 100644 index 0000000..f149f27 --- /dev/null +++ b/src/test/setup.ts @@ -0,0 +1 @@ +import "@testing-library/jest-dom/vitest"; diff --git a/src/types/domain.ts b/src/types/domain.ts new file mode 100644 index 0000000..07a7f35 --- /dev/null +++ b/src/types/domain.ts @@ -0,0 +1,37 @@ +export type Champion = { + id: string; + name: string; + role: "Controller" | "Duelist" | "Guardian" | "Invoker" | "Striker"; + difficulty: "Low" | "Medium" | "High"; + tags: string[]; + shortDescription: string; + accentColor?: string; +}; + +export type FriendStatus = "online" | "away" | "in-game" | "offline"; + +export type Friend = { + id: string; + displayName: string; + status: FriendStatus; + activity: string; + level?: number; +}; + +export type NewsItem = { + id: string; + title: string; + category: string; + date: string; + summary: string; +}; + +export type MatchHistoryEntry = { + id: string; + championName: string; + mode: string; + result: "Victory" | "Defeat"; + kda: string; + duration: string; + date: string; +}; diff --git a/src/types/navigation.ts b/src/types/navigation.ts new file mode 100644 index 0000000..2535f8a --- /dev/null +++ b/src/types/navigation.ts @@ -0,0 +1,16 @@ +export type NavigationSection = "primary" | "collection" | "account" | "system"; + +export type NavigationItem = { + id: string; + label: string; + path: string; + iconName?: string; + section?: NavigationSection; + enabled?: boolean; +}; + +export type AppRoute = { + id: string; + title: string; + path: string; +}; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a216c8b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ES2020"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src", "vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..78f1f4f --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,13 @@ +/// + +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [react()], + test: { + environment: "jsdom", + globals: true, + setupFiles: "./src/test/setup.ts" + } +});