diff --git a/.github/workflows/deployWebSample.yml b/.github/workflows/deployWebSample.yml new file mode 100644 index 0000000000..bb6c21fdfe --- /dev/null +++ b/.github/workflows/deployWebSample.yml @@ -0,0 +1,63 @@ +# Simple workflow for deploying Web SDK Sample to GitHub Pages +name: Deploy Web SDK Sample to Github Pages + +on: + pull_request: + branches: + - 'main' + paths: + - 'web/**' + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets the GITHUB_TOKEN permissions to allow comment on PRs +permissions: + contents: read + pull-requests: write + +# Allow one concurrent deployment +concurrency: + group: 'pages' + cancel-in-progress: true + +jobs: + # Single deploy job since we're just deploying + web-sdk-sample-deploy: + runs-on: ubuntu-latest + env: + SAMPLE_DIR: web/samples/WordWiz + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: '22.14.0' + + - name: Enable pnpm + run: | + corepack enable + corepack use pnpm@10.8.0 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + working-directory: ${{ env.SAMPLE_DIR }} + + - name: Build + run: pnpm run build + working-directory: ${{ env.SAMPLE_DIR }} + + - name: Publish Web SDK Sample Build + run: ./build/bin/upload-web-sdk-sample-build.sh + env: + GITHUB_USERNAME: ${{ secrets.GIT_WEB_SDK_SAMPLE_USERNAME }} + GITHUB_PASSWORD: ${{ secrets.GIT_WEB_SDK_SAMPLE_PASSWORD }} + SAMPLE_DIR: ${{ env.SAMPLE_DIR }} + + - uses: github-actions-up-and-running/pr-comment@v1.0.1 + if: ${{ github.event_name == 'pull_request' }} + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + message: Web SDK Sample Build [link](https://beamable.github.io/web-sdk-sample/) diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml index 56206b45f3..cfd7202967 100644 --- a/.github/workflows/release-web.yml +++ b/.github/workflows/release-web.yml @@ -71,7 +71,7 @@ jobs: # Install project dependencies using pnpm - name: Install dependencies - run: pnpm install + run: pnpm install --frozen-lockfile # Run code linter - name: Lint diff --git a/build/bin/upload-web-sdk-sample-build.sh b/build/bin/upload-web-sdk-sample-build.sh new file mode 100755 index 0000000000..8117fd2458 --- /dev/null +++ b/build/bin/upload-web-sdk-sample-build.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +# Exit immediately if any command fails +set -e + +# Use GitHub workspace if available, otherwise use current directory +workspace="${GITHUB_WORKSPACE:-.}" + +# Set Up Paths +build_directory="$workspace/web-sdk-sample-build" +github_repo_url="https://$GITHUB_USERNAME:$GITHUB_PASSWORD@github.com/beamable/web-sdk-sample.git" +source_dist_folder="$workspace/$SAMPLE_DIR/dist" + +# Validate Source Files Exist +if [[ ! -d "$source_dist_folder" ]]; then + echo "Error: Expected dist folder not found at $source_dist_folder" >&2 + exit 1 +fi + +echo "Preparing build directory..." + +# Create fresh build directory +mkdir -p "$build_directory" + +# Move into build directory +cd "$build_directory" + +echo "Setting up git repository..." + +git init +git config user.email "github-actions[bot]@users.noreply.github.com" +git config user.name "github-actions[bot]" +git remote add origin "$github_repo_url" + +# Fetch existing main branch or create new one +echo "Checking for existing repository..." +if git ls-remote --exit-code origin main &>/dev/null; then + echo "Found existing main branch, fetching..." + git fetch origin main --depth=1 + git checkout -B main origin/main +else + echo "No existing main branch found, creating new one..." + git checkout -B main +fi + +echo "Copying new files..." + +cp -a "$source_dist_folder"/. . + +echo "Checking for changes..." + +# Stage all changes +git add --all +git status + +# Check if there are any changes to commit +if git diff --cached --quiet; then + echo "No changes detected - nothing to commit" +else + echo "Changes detected, committing and pushing..." + git commit -m "Update web sdk sample build" + git push --set-upstream --force origin main + echo "Successfully deployed changes" +fi + +echo "Done!" \ No newline at end of file diff --git a/web/samples/WordWiz/.eslintrc.cjs b/web/samples/WordWiz/.eslintrc.cjs new file mode 100644 index 0000000000..ca385b7ba9 --- /dev/null +++ b/web/samples/WordWiz/.eslintrc.cjs @@ -0,0 +1,31 @@ +module.exports = { + env: { + browser: true, + es2021: true, + }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react/recommended', + ], + overrides: [ + { + env: { + node: true, + }, + files: ['.eslintrc.{js,cjs}'], + parserOptions: { + sourceType: 'script', + }, + }, + ], + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + plugins: ['@typescript-eslint', 'react'], + rules: { + 'react/react-in-jsx-scope': 0, + }, +}; diff --git a/web/samples/WordWiz/.gitignore b/web/samples/WordWiz/.gitignore new file mode 100644 index 0000000000..c74436aa16 --- /dev/null +++ b/web/samples/WordWiz/.gitignore @@ -0,0 +1,26 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.pem diff --git a/web/samples/WordWiz/.prettierignore b/web/samples/WordWiz/.prettierignore new file mode 100644 index 0000000000..40b878db5b --- /dev/null +++ b/web/samples/WordWiz/.prettierignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/web/samples/WordWiz/.prettierrc b/web/samples/WordWiz/.prettierrc new file mode 100644 index 0000000000..108b7935b9 --- /dev/null +++ b/web/samples/WordWiz/.prettierrc @@ -0,0 +1,6 @@ +{ + "printWidth": 80, + "bracketSpacing": true, + "semi": true, + "singleQuote": true +} diff --git a/web/samples/WordWiz/README.md b/web/samples/WordWiz/README.md new file mode 100644 index 0000000000..2fd52689f7 --- /dev/null +++ b/web/samples/WordWiz/README.md @@ -0,0 +1,123 @@ +# Word Wiz + +This template demonstrates how developers can implement a single-page +application on the Telegram Mini Apps platform using the following technologies +and libraries: + +- [React](https://react.dev/) +- [TypeScript](https://www.typescriptlang.org/) +- [Vite](https://vitejs.dev/) + +> The template was created using [npm](https://www.npmjs.com/). Therefore, it is +> required to use it for this project as well. Using other package managers, you +> will receive a corresponding error. + +## Install Dependencies + +If you have just cloned this template, you should install the project +dependencies using the command: + +```Bash +pnpm install +``` + +## Scripts + +This project contains the following scripts: + +- `dev`. Runs the application in development mode. +- `dev:https`. Runs the application in development mode using locally created valid SSL-certificates. +- `build`. Builds the application for production. +- `lint`. Runs [eslint](https://eslint.org/) to ensure the code quality meets + the required standards. +- `deploy`. Deploys the application to GitHub Pages. + +To run a script, use the `pnpm run` command: + +```Bash +pnpm run {script} +# Example: pnpm run build +``` + +## Deploy + +This boilerplate uses GitHub Pages as the way to host the application +externally. GitHub Pages provides a CDN which will let your users receive the +application rapidly. + +### Manual Deployment + +This boilerplate uses the [gh-pages](https://www.npmjs.com/package/gh-pages) +tool, which allows deploying the application right from the PC. + +#### Configuring + +Before running the deployment process, ensure that you have done the following: + +1. Replaced the `homepage` value in `package.json`. The GitHub Pages deploy tool + uses this value to + determine the related GitHub project. +2. Replaced the `base` value in `vite.config.ts` and have set it to the name of + your GitHub + repository. Vite will use this value when creating paths to static assets. + +For instance, if your GitHub username is `telegram-mini-apps` and the repository +name is `is-awesome`, the value in the `homepage` field should be the following: + +```json +{ + "homepage": "https://telegram-mini-apps.github.io/is-awesome" +} +``` + +And `vite.config.ts` should have this content: + +```ts +export default defineConfig({ + base: '/is-awesome/', + // ... +}); +``` + +You can find more information on configuring the deployment in the `gh-pages` +[docs](https://github.com/tschaub/gh-pages?tab=readme-ov-file#github-pages-project-sites). + +#### Before Deploying + +Before deploying the application, make sure that you've built it and going to +deploy the fresh static files: + +```bash +npm run build +``` + +Then, run the deployment process, using the `deploy` script: + +```Bash +npm run deploy +``` + +### GitHub Workflow + +To simplify the deployment process, this template includes a +pre-configured [GitHub workflow](.github/workflows/github-pages-deploy.yml) that +automatically deploys the project when changes are pushed to the `master` +branch. + +To enable this workflow, create a new environment (or edit the existing one) in +the GitHub repository settings and name it `github-pages`. Then, add the +`master` branch to the list of deployment branches. + +You can find the environment settings using this +URL: `https://github.com/{username}/{repository}/settings/environments`. + +![img.png](.github/deployment-branches.png) + +In case, you don't want to do it automatically, or you don't use GitHub as the +project codebase, remove the `.github` directory. + +### GitHub Web Interface + +Alternatively, developers can configure automatic deployment using the GitHub +web interface. To do this, follow the link: +`https://github.com/{username}/{repository}/settings/pages`. diff --git a/web/samples/WordWiz/eslint.config.js b/web/samples/WordWiz/eslint.config.js new file mode 100644 index 0000000000..25715399b1 --- /dev/null +++ b/web/samples/WordWiz/eslint.config.js @@ -0,0 +1,41 @@ +// @ts-check + +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import react from 'eslint-plugin-react'; +import reactHooks from 'eslint-plugin-react-hooks'; +import globals from 'globals'; + +const baseDir = path.dirname(fileURLToPath(import.meta.url)); + +export default tseslint.config({ + files: ['src/**/*.{js,jsx,mjs,cjs,ts,tsx}'], + extends: [ + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + ], + plugins: { + react, + 'react-hooks': reactHooks, + }, + languageOptions: { + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: baseDir, + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, + globals: { + ...globals.browser, + }, + }, + rules: { + '@typescript-eslint/no-unused-expressions': 0, + }, +}); diff --git a/web/samples/WordWiz/index.html b/web/samples/WordWiz/index.html new file mode 100644 index 0000000000..cfb8b7b7f3 --- /dev/null +++ b/web/samples/WordWiz/index.html @@ -0,0 +1,14 @@ + + + + + + + Word Wiz + + + +
+ + + diff --git a/web/samples/WordWiz/package.json b/web/samples/WordWiz/package.json new file mode 100644 index 0000000000..2d11ee55aa --- /dev/null +++ b/web/samples/WordWiz/package.json @@ -0,0 +1,47 @@ +{ + "name": "wordle-wiz", + "private": true, + "version": "0.0.1", + "type": "module", + "homepage": "https://BeamableProduct.github.io/BeamableProduct/WordWiz/", + "scripts": { + "deploy": "gh-pages -d dist", + "dev": "vite", + "dev:https": "cross-env HTTPS=true vite", + "build": "tsc --noEmit && vite build", + "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "lint:fix": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0 --fix", + "preview": "vite preview", + "predeploy": "npm run build", + "test": "vitest", + "format": "prettier --write ." + }, + "dependencies": { + "beamable-sdk": "file:../..", + "prettier": "^3.6.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.23.0" + }, + "devDependencies": { + "@eslint/js": "^9.23.0", + "@types/node": "^20.12.11", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@typescript-eslint/eslint-plugin": "^8.28.0", + "@typescript-eslint/parser": "^8.28.0", + "@vitejs/plugin-react-swc": "^3.8.1", + "cross-env": "^7.0.3", + "eslint": "^9.23.0", + "eslint-plugin-react": "^7.37.4", + "eslint-plugin-react-hooks": "^5.2.0", + "gh-pages": "^6.1.1", + "globals": "^15.2.0", + "typescript": "^5.8.2", + "typescript-eslint": "^8.28.0", + "vite": "^6.2.4", + "vite-plugin-mkcert": "^1.17.8", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.2.4" + } +} diff --git a/web/samples/WordWiz/pnpm-lock.yaml b/web/samples/WordWiz/pnpm-lock.yaml new file mode 100644 index 0000000000..4857855df6 --- /dev/null +++ b/web/samples/WordWiz/pnpm-lock.yaml @@ -0,0 +1,4977 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + .: + dependencies: + beamable-sdk: + specifier: file:../.. + version: file:../.. + prettier: + specifier: ^3.6.2 + version: 3.6.2 + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + react-router-dom: + specifier: ^6.23.0 + version: 6.30.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + devDependencies: + '@eslint/js': + specifier: ^9.23.0 + version: 9.36.0 + '@types/node': + specifier: ^20.12.11 + version: 20.19.17 + '@types/react': + specifier: ^18.2.0 + version: 18.3.24 + '@types/react-dom': + specifier: ^18.2.0 + version: 18.3.7(@types/react@18.3.24) + '@typescript-eslint/eslint-plugin': + specifier: ^8.28.0 + version: 8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0)(typescript@5.9.2))(eslint@9.36.0)(typescript@5.9.2) + '@typescript-eslint/parser': + specifier: ^8.28.0 + version: 8.44.1(eslint@9.36.0)(typescript@5.9.2) + '@vitejs/plugin-react-swc': + specifier: ^3.8.1 + version: 3.11.0(vite@6.3.6(@types/node@20.19.17)) + cross-env: + specifier: ^7.0.3 + version: 7.0.3 + eslint: + specifier: ^9.23.0 + version: 9.36.0 + eslint-plugin-react: + specifier: ^7.37.4 + version: 7.37.5(eslint@9.36.0) + eslint-plugin-react-hooks: + specifier: ^5.2.0 + version: 5.2.0(eslint@9.36.0) + gh-pages: + specifier: ^6.1.1 + version: 6.3.0 + globals: + specifier: ^15.2.0 + version: 15.15.0 + typescript: + specifier: ^5.8.2 + version: 5.9.2 + typescript-eslint: + specifier: ^8.28.0 + version: 8.44.1(eslint@9.36.0)(typescript@5.9.2) + vite: + specifier: ^6.2.4 + version: 6.3.6(@types/node@20.19.17) + vite-plugin-mkcert: + specifier: ^1.17.8 + version: 1.17.8(vite@6.3.6(@types/node@20.19.17)) + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.9.2)(vite@6.3.6(@types/node@20.19.17)) + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/node@20.19.17) + +packages: + '@esbuild/aix-ppc64@0.25.10': + resolution: + { + integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==, + } + engines: { node: '>=18' } + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.10': + resolution: + { + integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.10': + resolution: + { + integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==, + } + engines: { node: '>=18' } + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.10': + resolution: + { + integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.10': + resolution: + { + integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.10': + resolution: + { + integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.10': + resolution: + { + integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.10': + resolution: + { + integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.10': + resolution: + { + integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.10': + resolution: + { + integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==, + } + engines: { node: '>=18' } + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.10': + resolution: + { + integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==, + } + engines: { node: '>=18' } + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.10': + resolution: + { + integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==, + } + engines: { node: '>=18' } + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.10': + resolution: + { + integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==, + } + engines: { node: '>=18' } + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.10': + resolution: + { + integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==, + } + engines: { node: '>=18' } + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.10': + resolution: + { + integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==, + } + engines: { node: '>=18' } + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.10': + resolution: + { + integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==, + } + engines: { node: '>=18' } + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.10': + resolution: + { + integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.10': + resolution: + { + integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.10': + resolution: + { + integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.10': + resolution: + { + integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.10': + resolution: + { + integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.10': + resolution: + { + integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.10': + resolution: + { + integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.10': + resolution: + { + integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.10': + resolution: + { + integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==, + } + engines: { node: '>=18' } + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.10': + resolution: + { + integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.0': + resolution: + { + integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: + { + integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==, + } + engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } + + '@eslint/config-array@0.21.0': + resolution: + { + integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@eslint/config-helpers@0.3.1': + resolution: + { + integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@eslint/core@0.15.2': + resolution: + { + integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@eslint/eslintrc@3.3.1': + resolution: + { + integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@eslint/js@9.36.0': + resolution: + { + integrity: sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@eslint/object-schema@2.1.6': + resolution: + { + integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@eslint/plugin-kit@0.3.5': + resolution: + { + integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@humanfs/core@0.19.1': + resolution: + { + integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==, + } + engines: { node: '>=18.18.0' } + + '@humanfs/node@0.16.7': + resolution: + { + integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==, + } + engines: { node: '>=18.18.0' } + + '@humanwhocodes/module-importer@1.0.1': + resolution: + { + integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==, + } + engines: { node: '>=12.22' } + + '@humanwhocodes/retry@0.4.3': + resolution: + { + integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==, + } + engines: { node: '>=18.18' } + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: + { + integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==, + } + + '@nodelib/fs.scandir@2.1.5': + resolution: + { + integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, + } + engines: { node: '>= 8' } + + '@nodelib/fs.stat@2.0.5': + resolution: + { + integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==, + } + engines: { node: '>= 8' } + + '@nodelib/fs.walk@1.2.8': + resolution: + { + integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==, + } + engines: { node: '>= 8' } + + '@remix-run/router@1.23.0': + resolution: + { + integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==, + } + engines: { node: '>=14.0.0' } + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: + { + integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==, + } + + '@rollup/rollup-android-arm-eabi@4.52.2': + resolution: + { + integrity: sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==, + } + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.52.2': + resolution: + { + integrity: sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==, + } + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.52.2': + resolution: + { + integrity: sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==, + } + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.52.2': + resolution: + { + integrity: sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==, + } + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.52.2': + resolution: + { + integrity: sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==, + } + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.52.2': + resolution: + { + integrity: sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==, + } + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.52.2': + resolution: + { + integrity: sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==, + } + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.52.2': + resolution: + { + integrity: sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==, + } + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.52.2': + resolution: + { + integrity: sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==, + } + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.52.2': + resolution: + { + integrity: sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==, + } + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.52.2': + resolution: + { + integrity: sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==, + } + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.52.2': + resolution: + { + integrity: sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==, + } + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.52.2': + resolution: + { + integrity: sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==, + } + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.52.2': + resolution: + { + integrity: sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==, + } + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.52.2': + resolution: + { + integrity: sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==, + } + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.52.2': + resolution: + { + integrity: sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==, + } + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.52.2': + resolution: + { + integrity: sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==, + } + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.52.2': + resolution: + { + integrity: sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==, + } + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.52.2': + resolution: + { + integrity: sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==, + } + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.52.2': + resolution: + { + integrity: sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==, + } + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.52.2': + resolution: + { + integrity: sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==, + } + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.52.2': + resolution: + { + integrity: sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==, + } + cpu: [x64] + os: [win32] + + '@swc/core-darwin-arm64@1.13.19': + resolution: + { + integrity: sha512-NxDyte9tCJSJ8+R62WDtqwg8eI57lubD52sHyGOfezpJBOPr36bUSGGLyO3Vod9zTGlOu2CpkuzA/2iVw92u1g==, + } + engines: { node: '>=10' } + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.13.19': + resolution: + { + integrity: sha512-+w5DYrJndSygFFRDcuPYmx5BljD6oYnAohZ15K1L6SfORHp/BTSIbgSFRKPoyhjuIkDiq3W0um8RoMTOBAcQjQ==, + } + engines: { node: '>=10' } + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.13.19': + resolution: + { + integrity: sha512-7LlfgpdwwYq2q7himNkAAFo4q6jysMLFNoBH6GRP7WL29NcSsl5mPMJjmYZymK+sYq/9MTVieDTQvChzYDsapw==, + } + engines: { node: '>=10' } + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.13.19': + resolution: + { + integrity: sha512-ml3I6Lm2marAQ3UC/TS9t/yILBh/eDSVHAdPpikp652xouWAVW1znUeV6bBSxe1sSZIenv+p55ubKAWq/u84sQ==, + } + engines: { node: '>=10' } + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.13.19': + resolution: + { + integrity: sha512-M/otFc3/rWWkbF6VgbOXVzUKVoE7MFcphTaStxJp4bwb7oP5slYlxMZN51Dk/OTOfvCDo9pTAFDKNyixbkXMDQ==, + } + engines: { node: '>=10' } + cpu: [arm64] + os: [linux] + + '@swc/core-linux-x64-gnu@1.13.19': + resolution: + { + integrity: sha512-NoMUKaOJEdouU4tKF88ggdDHFiRRING+gYLxDqnTfm+sUXaizB5OGBRzvSVDYSXQb1SuUuChnXFPFzwTWbt3ZQ==, + } + engines: { node: '>=10' } + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.13.19': + resolution: + { + integrity: sha512-r6krlZwyu8SBaw24QuS1lau2I9q8M+eJV6ITz0rpb6P1Bx0elf9ii5Bhh8ddmIqXXH8kOGSjC/dwcdHbZqAhgw==, + } + engines: { node: '>=10' } + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.13.19': + resolution: + { + integrity: sha512-awcZSIuxyVn0Dw28VjMvgk1qiDJ6CeQwHkZNUjg2UxVlq23zE01NMMp+zkoGFypmLG9gaGmJSzuoqvk/WCQ5tw==, + } + engines: { node: '>=10' } + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.13.19': + resolution: + { + integrity: sha512-H5d+KO7ISoLNgYvTbOcCQjJZNM3R7yaYlrMAF13lUr6GSiOUX+92xtM31B+HvzAWI7HtvVe74d29aC1b1TpXFA==, + } + engines: { node: '>=10' } + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.13.19': + resolution: + { + integrity: sha512-qNoyCpXvv2O3JqXKanRIeoMn03Fho/As+N4Fhe7u0FsYh4VYqGQah4DGDzEP/yjl4Gx1IElhqLGDhCCGMwWaDw==, + } + engines: { node: '>=10' } + cpu: [x64] + os: [win32] + + '@swc/core@1.13.19': + resolution: + { + integrity: sha512-V1r4wFdjaZIUIZZrV2Mb/prEeu03xvSm6oatPxsvnXKF9lNh5Jtk9QvUdiVfD9rrvi7bXrAVhg9Wpbmv/2Fl1g==, + } + engines: { node: '>=10' } + peerDependencies: + '@swc/helpers': '>=0.5.17' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: + { + integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==, + } + + '@swc/types@0.1.25': + resolution: + { + integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==, + } + + '@types/chai@5.2.2': + resolution: + { + integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==, + } + + '@types/deep-eql@4.0.2': + resolution: + { + integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==, + } + + '@types/estree@1.0.8': + resolution: + { + integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==, + } + + '@types/json-schema@7.0.15': + resolution: + { + integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, + } + + '@types/node@20.19.17': + resolution: + { + integrity: sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==, + } + + '@types/prop-types@15.7.15': + resolution: + { + integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==, + } + + '@types/react-dom@18.3.7': + resolution: + { + integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==, + } + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.24': + resolution: + { + integrity: sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==, + } + + '@typescript-eslint/eslint-plugin@8.44.1': + resolution: + { + integrity: sha512-molgphGqOBT7t4YKCSkbasmu1tb1MgrZ2szGzHbclF7PNmOkSTQVHy+2jXOSnxvR3+Xe1yySHFZoqMpz3TfQsw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + '@typescript-eslint/parser': ^8.44.1 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.44.1': + resolution: + { + integrity: sha512-EHrrEsyhOhxYt8MTg4zTF+DJMuNBzWwgvvOYNj/zm1vnaD/IC5zCXFehZv94Piqa2cRFfXrTFxIvO95L7Qc/cw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.44.1': + resolution: + { + integrity: sha512-ycSa60eGg8GWAkVsKV4E6Nz33h+HjTXbsDT4FILyL8Obk5/mx4tbvCNsLf9zret3ipSumAOG89UcCs/KRaKYrA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.44.1': + resolution: + { + integrity: sha512-NdhWHgmynpSvyhchGLXh+w12OMT308Gm25JoRIyTZqEbApiBiQHD/8xgb6LqCWCFcxFtWwaVdFsLPQI3jvhywg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@typescript-eslint/tsconfig-utils@8.44.1': + resolution: + { + integrity: sha512-B5OyACouEjuIvof3o86lRMvyDsFwZm+4fBOqFHccIctYgBjqR3qT39FBYGN87khcgf0ExpdCBeGKpKRhSFTjKQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.44.1': + resolution: + { + integrity: sha512-KdEerZqHWXsRNKjF9NYswNISnFzXfXNDfPxoTh7tqohU/PRIbwTmsjGK6V9/RTYWau7NZvfo52lgVk+sJh0K3g==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.44.1': + resolution: + { + integrity: sha512-Lk7uj7y9uQUOEguiDIDLYLJOrYHQa7oBiURYVFqIpGxclAFQ78f6VUOM8lI2XEuNOKNB7XuvM2+2cMXAoq4ALQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@typescript-eslint/typescript-estree@8.44.1': + resolution: + { + integrity: sha512-qnQJ+mVa7szevdEyvfItbO5Vo+GfZ4/GZWWDRRLjrxYPkhM+6zYB2vRYwCsoJLzqFCdZT4mEqyJoyzkunsZ96A==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.44.1': + resolution: + { + integrity: sha512-DpX5Fp6edTlocMCwA+mHY8Mra+pPjRZ0TfHkXI8QFelIKcbADQz1LUPNtzOFUriBB2UYqw4Pi9+xV4w9ZczHFg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.44.1': + resolution: + { + integrity: sha512-576+u0QD+Jp3tZzvfRfxon0EA2lzcDt3lhUbsC6Lgzy9x2VR4E+JUiNyGHi5T8vk0TV+fpJ5GLG1JsJuWCaKhw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@vitejs/plugin-react-swc@3.11.0': + resolution: + { + integrity: sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==, + } + peerDependencies: + vite: ^4 || ^5 || ^6 || ^7 + + '@vitest/expect@3.2.4': + resolution: + { + integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==, + } + + '@vitest/mocker@3.2.4': + resolution: + { + integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==, + } + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: + { + integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==, + } + + '@vitest/runner@3.2.4': + resolution: + { + integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==, + } + + '@vitest/snapshot@3.2.4': + resolution: + { + integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==, + } + + '@vitest/spy@3.2.4': + resolution: + { + integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==, + } + + '@vitest/utils@3.2.4': + resolution: + { + integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==, + } + + acorn-jsx@5.3.2: + resolution: + { + integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, + } + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: + { + integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==, + } + engines: { node: '>=0.4.0' } + hasBin: true + + ajv@6.12.6: + resolution: + { + integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==, + } + + ansi-styles@4.3.0: + resolution: + { + integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, + } + engines: { node: '>=8' } + + argparse@2.0.1: + resolution: + { + integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, + } + + array-buffer-byte-length@1.0.2: + resolution: + { + integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==, + } + engines: { node: '>= 0.4' } + + array-includes@3.1.9: + resolution: + { + integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==, + } + engines: { node: '>= 0.4' } + + array-union@2.1.0: + resolution: + { + integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==, + } + engines: { node: '>=8' } + + array.prototype.findlast@1.2.5: + resolution: + { + integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==, + } + engines: { node: '>= 0.4' } + + array.prototype.flat@1.3.3: + resolution: + { + integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==, + } + engines: { node: '>= 0.4' } + + array.prototype.flatmap@1.3.3: + resolution: + { + integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==, + } + engines: { node: '>= 0.4' } + + array.prototype.tosorted@1.1.4: + resolution: + { + integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==, + } + engines: { node: '>= 0.4' } + + arraybuffer.prototype.slice@1.0.4: + resolution: + { + integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==, + } + engines: { node: '>= 0.4' } + + assertion-error@2.0.1: + resolution: + { + integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==, + } + engines: { node: '>=12' } + + async-function@1.0.0: + resolution: + { + integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==, + } + engines: { node: '>= 0.4' } + + async@3.2.6: + resolution: + { + integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==, + } + + asynckit@0.4.0: + resolution: + { + integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==, + } + + available-typed-arrays@1.0.7: + resolution: + { + integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==, + } + engines: { node: '>= 0.4' } + + axios@1.12.2: + resolution: + { + integrity: sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==, + } + + balanced-match@1.0.2: + resolution: + { + integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, + } + + beamable-sdk@file:../..: + resolution: { directory: ../.., type: directory } + engines: { node: '>=22.14.0' } + + brace-expansion@1.1.12: + resolution: + { + integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==, + } + + brace-expansion@2.0.2: + resolution: + { + integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==, + } + + braces@3.0.3: + resolution: + { + integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==, + } + engines: { node: '>=8' } + + cac@6.7.14: + resolution: + { + integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==, + } + engines: { node: '>=8' } + + call-bind-apply-helpers@1.0.2: + resolution: + { + integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==, + } + engines: { node: '>= 0.4' } + + call-bind@1.0.8: + resolution: + { + integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==, + } + engines: { node: '>= 0.4' } + + call-bound@1.0.4: + resolution: + { + integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==, + } + engines: { node: '>= 0.4' } + + callsites@3.1.0: + resolution: + { + integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, + } + engines: { node: '>=6' } + + chai@5.3.3: + resolution: + { + integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==, + } + engines: { node: '>=18' } + + chalk@4.1.2: + resolution: + { + integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, + } + engines: { node: '>=10' } + + check-error@2.1.1: + resolution: + { + integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==, + } + engines: { node: '>= 16' } + + color-convert@2.0.1: + resolution: + { + integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, + } + engines: { node: '>=7.0.0' } + + color-name@1.1.4: + resolution: + { + integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, + } + + combined-stream@1.0.8: + resolution: + { + integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==, + } + engines: { node: '>= 0.8' } + + commander@13.1.0: + resolution: + { + integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==, + } + engines: { node: '>=18' } + + commondir@1.0.1: + resolution: + { + integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==, + } + + concat-map@0.0.1: + resolution: + { + integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, + } + + cross-env@7.0.3: + resolution: + { + integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==, + } + engines: { node: '>=10.14', npm: '>=6', yarn: '>=1' } + hasBin: true + + cross-spawn@7.0.6: + resolution: + { + integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, + } + engines: { node: '>= 8' } + + csstype@3.1.3: + resolution: + { + integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==, + } + + data-view-buffer@1.0.2: + resolution: + { + integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==, + } + engines: { node: '>= 0.4' } + + data-view-byte-length@1.0.2: + resolution: + { + integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==, + } + engines: { node: '>= 0.4' } + + data-view-byte-offset@1.0.1: + resolution: + { + integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==, + } + engines: { node: '>= 0.4' } + + debug@4.4.3: + resolution: + { + integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==, + } + engines: { node: '>=6.0' } + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: + { + integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==, + } + engines: { node: '>=6' } + + deep-is@0.1.4: + resolution: + { + integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, + } + + define-data-property@1.1.4: + resolution: + { + integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==, + } + engines: { node: '>= 0.4' } + + define-properties@1.2.1: + resolution: + { + integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==, + } + engines: { node: '>= 0.4' } + + delayed-stream@1.0.0: + resolution: + { + integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==, + } + engines: { node: '>=0.4.0' } + + dir-glob@3.0.1: + resolution: + { + integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==, + } + engines: { node: '>=8' } + + doctrine@2.1.0: + resolution: + { + integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==, + } + engines: { node: '>=0.10.0' } + + dunder-proto@1.0.1: + resolution: + { + integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==, + } + engines: { node: '>= 0.4' } + + email-addresses@5.0.0: + resolution: + { + integrity: sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==, + } + + es-abstract@1.24.0: + resolution: + { + integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==, + } + engines: { node: '>= 0.4' } + + es-define-property@1.0.1: + resolution: + { + integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==, + } + engines: { node: '>= 0.4' } + + es-errors@1.3.0: + resolution: + { + integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==, + } + engines: { node: '>= 0.4' } + + es-iterator-helpers@1.2.1: + resolution: + { + integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==, + } + engines: { node: '>= 0.4' } + + es-module-lexer@1.7.0: + resolution: + { + integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==, + } + + es-object-atoms@1.1.1: + resolution: + { + integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==, + } + engines: { node: '>= 0.4' } + + es-set-tostringtag@2.1.0: + resolution: + { + integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==, + } + engines: { node: '>= 0.4' } + + es-shim-unscopables@1.1.0: + resolution: + { + integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==, + } + engines: { node: '>= 0.4' } + + es-to-primitive@1.3.0: + resolution: + { + integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==, + } + engines: { node: '>= 0.4' } + + esbuild@0.25.10: + resolution: + { + integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==, + } + engines: { node: '>=18' } + hasBin: true + + escape-string-regexp@1.0.5: + resolution: + { + integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==, + } + engines: { node: '>=0.8.0' } + + escape-string-regexp@4.0.0: + resolution: + { + integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, + } + engines: { node: '>=10' } + + eslint-plugin-react-hooks@5.2.0: + resolution: + { + integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==, + } + engines: { node: '>=10' } + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react@7.37.5: + resolution: + { + integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==, + } + engines: { node: '>=4' } + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-scope@8.4.0: + resolution: + { + integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + eslint-visitor-keys@3.4.3: + resolution: + { + integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + + eslint-visitor-keys@4.2.1: + resolution: + { + integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + eslint@9.36.0: + resolution: + { + integrity: sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: + { + integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + esquery@1.6.0: + resolution: + { + integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==, + } + engines: { node: '>=0.10' } + + esrecurse@4.3.0: + resolution: + { + integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==, + } + engines: { node: '>=4.0' } + + estraverse@5.3.0: + resolution: + { + integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==, + } + engines: { node: '>=4.0' } + + estree-walker@3.0.3: + resolution: + { + integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==, + } + + esutils@2.0.3: + resolution: + { + integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, + } + engines: { node: '>=0.10.0' } + + expect-type@1.2.2: + resolution: + { + integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==, + } + engines: { node: '>=12.0.0' } + + fast-deep-equal@3.1.3: + resolution: + { + integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, + } + + fast-glob@3.3.3: + resolution: + { + integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==, + } + engines: { node: '>=8.6.0' } + + fast-json-stable-stringify@2.1.0: + resolution: + { + integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, + } + + fast-levenshtein@2.0.6: + resolution: + { + integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, + } + + fastq@1.19.1: + resolution: + { + integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==, + } + + fdir@6.5.0: + resolution: + { + integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==, + } + engines: { node: '>=12.0.0' } + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: + { + integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==, + } + engines: { node: '>=16.0.0' } + + filename-reserved-regex@2.0.0: + resolution: + { + integrity: sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==, + } + engines: { node: '>=4' } + + filenamify@4.3.0: + resolution: + { + integrity: sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==, + } + engines: { node: '>=8' } + + fill-range@7.1.1: + resolution: + { + integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==, + } + engines: { node: '>=8' } + + find-cache-dir@3.3.2: + resolution: + { + integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==, + } + engines: { node: '>=8' } + + find-up@4.1.0: + resolution: + { + integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==, + } + engines: { node: '>=8' } + + find-up@5.0.0: + resolution: + { + integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==, + } + engines: { node: '>=10' } + + flat-cache@4.0.1: + resolution: + { + integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==, + } + engines: { node: '>=16' } + + flatted@3.3.3: + resolution: + { + integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==, + } + + follow-redirects@1.15.11: + resolution: + { + integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==, + } + engines: { node: '>=4.0' } + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.5: + resolution: + { + integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==, + } + engines: { node: '>= 0.4' } + + form-data@4.0.4: + resolution: + { + integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==, + } + engines: { node: '>= 6' } + + fs-extra@11.3.2: + resolution: + { + integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==, + } + engines: { node: '>=14.14' } + + fsevents@2.3.3: + resolution: + { + integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, + } + engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + os: [darwin] + + function-bind@1.1.2: + resolution: + { + integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==, + } + + function.prototype.name@1.1.8: + resolution: + { + integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==, + } + engines: { node: '>= 0.4' } + + functions-have-names@1.2.3: + resolution: + { + integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==, + } + + get-intrinsic@1.3.0: + resolution: + { + integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==, + } + engines: { node: '>= 0.4' } + + get-proto@1.0.1: + resolution: + { + integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==, + } + engines: { node: '>= 0.4' } + + get-symbol-description@1.1.0: + resolution: + { + integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==, + } + engines: { node: '>= 0.4' } + + gh-pages@6.3.0: + resolution: + { + integrity: sha512-Ot5lU6jK0Eb+sszG8pciXdjMXdBJ5wODvgjR+imihTqsUWF2K6dJ9HST55lgqcs8wWcw6o6wAsUzfcYRhJPXbA==, + } + engines: { node: '>=10' } + hasBin: true + + glob-parent@5.1.2: + resolution: + { + integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==, + } + engines: { node: '>= 6' } + + glob-parent@6.0.2: + resolution: + { + integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, + } + engines: { node: '>=10.13.0' } + + globals@14.0.0: + resolution: + { + integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==, + } + engines: { node: '>=18' } + + globals@15.15.0: + resolution: + { + integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==, + } + engines: { node: '>=18' } + + globalthis@1.0.4: + resolution: + { + integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==, + } + engines: { node: '>= 0.4' } + + globby@11.1.0: + resolution: + { + integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==, + } + engines: { node: '>=10' } + + globrex@0.1.2: + resolution: + { + integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==, + } + + gopd@1.2.0: + resolution: + { + integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==, + } + engines: { node: '>= 0.4' } + + graceful-fs@4.2.11: + resolution: + { + integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==, + } + + graphemer@1.4.0: + resolution: + { + integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==, + } + + has-bigints@1.1.0: + resolution: + { + integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==, + } + engines: { node: '>= 0.4' } + + has-flag@4.0.0: + resolution: + { + integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, + } + engines: { node: '>=8' } + + has-property-descriptors@1.0.2: + resolution: + { + integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==, + } + + has-proto@1.2.0: + resolution: + { + integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==, + } + engines: { node: '>= 0.4' } + + has-symbols@1.1.0: + resolution: + { + integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==, + } + engines: { node: '>= 0.4' } + + has-tostringtag@1.0.2: + resolution: + { + integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==, + } + engines: { node: '>= 0.4' } + + hasown@2.0.2: + resolution: + { + integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==, + } + engines: { node: '>= 0.4' } + + ignore@5.3.2: + resolution: + { + integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==, + } + engines: { node: '>= 4' } + + ignore@7.0.5: + resolution: + { + integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==, + } + engines: { node: '>= 4' } + + import-fresh@3.3.1: + resolution: + { + integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==, + } + engines: { node: '>=6' } + + imurmurhash@0.1.4: + resolution: + { + integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==, + } + engines: { node: '>=0.8.19' } + + internal-slot@1.1.0: + resolution: + { + integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==, + } + engines: { node: '>= 0.4' } + + is-array-buffer@3.0.5: + resolution: + { + integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==, + } + engines: { node: '>= 0.4' } + + is-async-function@2.1.1: + resolution: + { + integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==, + } + engines: { node: '>= 0.4' } + + is-bigint@1.1.0: + resolution: + { + integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==, + } + engines: { node: '>= 0.4' } + + is-boolean-object@1.2.2: + resolution: + { + integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==, + } + engines: { node: '>= 0.4' } + + is-callable@1.2.7: + resolution: + { + integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==, + } + engines: { node: '>= 0.4' } + + is-core-module@2.16.1: + resolution: + { + integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==, + } + engines: { node: '>= 0.4' } + + is-data-view@1.0.2: + resolution: + { + integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==, + } + engines: { node: '>= 0.4' } + + is-date-object@1.1.0: + resolution: + { + integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==, + } + engines: { node: '>= 0.4' } + + is-extglob@2.1.1: + resolution: + { + integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, + } + engines: { node: '>=0.10.0' } + + is-finalizationregistry@1.1.1: + resolution: + { + integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==, + } + engines: { node: '>= 0.4' } + + is-generator-function@1.1.0: + resolution: + { + integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==, + } + engines: { node: '>= 0.4' } + + is-glob@4.0.3: + resolution: + { + integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, + } + engines: { node: '>=0.10.0' } + + is-map@2.0.3: + resolution: + { + integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==, + } + engines: { node: '>= 0.4' } + + is-negative-zero@2.0.3: + resolution: + { + integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==, + } + engines: { node: '>= 0.4' } + + is-number-object@1.1.1: + resolution: + { + integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==, + } + engines: { node: '>= 0.4' } + + is-number@7.0.0: + resolution: + { + integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, + } + engines: { node: '>=0.12.0' } + + is-regex@1.2.1: + resolution: + { + integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==, + } + engines: { node: '>= 0.4' } + + is-set@2.0.3: + resolution: + { + integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==, + } + engines: { node: '>= 0.4' } + + is-shared-array-buffer@1.0.4: + resolution: + { + integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==, + } + engines: { node: '>= 0.4' } + + is-string@1.1.1: + resolution: + { + integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==, + } + engines: { node: '>= 0.4' } + + is-symbol@1.1.1: + resolution: + { + integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==, + } + engines: { node: '>= 0.4' } + + is-typed-array@1.1.15: + resolution: + { + integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==, + } + engines: { node: '>= 0.4' } + + is-weakmap@2.0.2: + resolution: + { + integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==, + } + engines: { node: '>= 0.4' } + + is-weakref@1.1.1: + resolution: + { + integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==, + } + engines: { node: '>= 0.4' } + + is-weakset@2.0.4: + resolution: + { + integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==, + } + engines: { node: '>= 0.4' } + + isarray@2.0.5: + resolution: + { + integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==, + } + + isexe@2.0.0: + resolution: + { + integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, + } + + iterator.prototype@1.1.5: + resolution: + { + integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==, + } + engines: { node: '>= 0.4' } + + js-tokens@4.0.0: + resolution: + { + integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, + } + + js-tokens@9.0.1: + resolution: + { + integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==, + } + + js-yaml@4.1.0: + resolution: + { + integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==, + } + hasBin: true + + json-buffer@3.0.1: + resolution: + { + integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==, + } + + json-schema-traverse@0.4.1: + resolution: + { + integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, + } + + json-stable-stringify-without-jsonify@1.0.1: + resolution: + { + integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, + } + + jsonfile@6.2.0: + resolution: + { + integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==, + } + + jsx-ast-utils@3.3.5: + resolution: + { + integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==, + } + engines: { node: '>=4.0' } + + keyv@4.5.4: + resolution: + { + integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, + } + + levn@0.4.1: + resolution: + { + integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, + } + engines: { node: '>= 0.8.0' } + + locate-path@5.0.0: + resolution: + { + integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==, + } + engines: { node: '>=8' } + + locate-path@6.0.0: + resolution: + { + integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==, + } + engines: { node: '>=10' } + + lodash.merge@4.6.2: + resolution: + { + integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, + } + + loose-envify@1.4.0: + resolution: + { + integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, + } + hasBin: true + + loupe@3.2.1: + resolution: + { + integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==, + } + + magic-string@0.30.19: + resolution: + { + integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==, + } + + make-dir@3.1.0: + resolution: + { + integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==, + } + engines: { node: '>=8' } + + math-intrinsics@1.1.0: + resolution: + { + integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==, + } + engines: { node: '>= 0.4' } + + merge2@1.4.1: + resolution: + { + integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==, + } + engines: { node: '>= 8' } + + micromatch@4.0.8: + resolution: + { + integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==, + } + engines: { node: '>=8.6' } + + mime-db@1.52.0: + resolution: + { + integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==, + } + engines: { node: '>= 0.6' } + + mime-types@2.1.35: + resolution: + { + integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==, + } + engines: { node: '>= 0.6' } + + minimatch@3.1.2: + resolution: + { + integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, + } + + minimatch@9.0.5: + resolution: + { + integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==, + } + engines: { node: '>=16 || 14 >=14.17' } + + ms@2.1.3: + resolution: + { + integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, + } + + nanoid@3.3.11: + resolution: + { + integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==, + } + engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + hasBin: true + + natural-compare@1.4.0: + resolution: + { + integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, + } + + object-assign@4.1.1: + resolution: + { + integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==, + } + engines: { node: '>=0.10.0' } + + object-inspect@1.13.4: + resolution: + { + integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==, + } + engines: { node: '>= 0.4' } + + object-keys@1.1.1: + resolution: + { + integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==, + } + engines: { node: '>= 0.4' } + + object.assign@4.1.7: + resolution: + { + integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==, + } + engines: { node: '>= 0.4' } + + object.entries@1.1.9: + resolution: + { + integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==, + } + engines: { node: '>= 0.4' } + + object.fromentries@2.0.8: + resolution: + { + integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==, + } + engines: { node: '>= 0.4' } + + object.values@1.2.1: + resolution: + { + integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==, + } + engines: { node: '>= 0.4' } + + optionator@0.9.4: + resolution: + { + integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==, + } + engines: { node: '>= 0.8.0' } + + own-keys@1.0.1: + resolution: + { + integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==, + } + engines: { node: '>= 0.4' } + + p-limit@2.3.0: + resolution: + { + integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==, + } + engines: { node: '>=6' } + + p-limit@3.1.0: + resolution: + { + integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==, + } + engines: { node: '>=10' } + + p-locate@4.1.0: + resolution: + { + integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==, + } + engines: { node: '>=8' } + + p-locate@5.0.0: + resolution: + { + integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==, + } + engines: { node: '>=10' } + + p-try@2.2.0: + resolution: + { + integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==, + } + engines: { node: '>=6' } + + parent-module@1.0.1: + resolution: + { + integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, + } + engines: { node: '>=6' } + + path-exists@4.0.0: + resolution: + { + integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, + } + engines: { node: '>=8' } + + path-key@3.1.1: + resolution: + { + integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, + } + engines: { node: '>=8' } + + path-parse@1.0.7: + resolution: + { + integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, + } + + path-type@4.0.0: + resolution: + { + integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==, + } + engines: { node: '>=8' } + + pathe@2.0.3: + resolution: + { + integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, + } + + pathval@2.0.1: + resolution: + { + integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==, + } + engines: { node: '>= 14.16' } + + picocolors@1.1.1: + resolution: + { + integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, + } + + picomatch@2.3.1: + resolution: + { + integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==, + } + engines: { node: '>=8.6' } + + picomatch@4.0.3: + resolution: + { + integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==, + } + engines: { node: '>=12' } + + pkg-dir@4.2.0: + resolution: + { + integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==, + } + engines: { node: '>=8' } + + possible-typed-array-names@1.1.0: + resolution: + { + integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==, + } + engines: { node: '>= 0.4' } + + postcss@8.5.6: + resolution: + { + integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==, + } + engines: { node: ^10 || ^12 || >=14 } + + prelude-ls@1.2.1: + resolution: + { + integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, + } + engines: { node: '>= 0.8.0' } + + prettier@3.6.2: + resolution: + { + integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==, + } + engines: { node: '>=14' } + hasBin: true + + prop-types@15.8.1: + resolution: + { + integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==, + } + + proxy-from-env@1.1.0: + resolution: + { + integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==, + } + + punycode@2.3.1: + resolution: + { + integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==, + } + engines: { node: '>=6' } + + queue-microtask@1.2.3: + resolution: + { + integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, + } + + react-dom@18.3.1: + resolution: + { + integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==, + } + peerDependencies: + react: ^18.3.1 + + react-is@16.13.1: + resolution: + { + integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==, + } + + react-router-dom@6.30.1: + resolution: + { + integrity: sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==, + } + engines: { node: '>=14.0.0' } + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + react-router@6.30.1: + resolution: + { + integrity: sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==, + } + engines: { node: '>=14.0.0' } + peerDependencies: + react: '>=16.8' + + react@18.3.1: + resolution: + { + integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==, + } + engines: { node: '>=0.10.0' } + + reflect.getprototypeof@1.0.10: + resolution: + { + integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==, + } + engines: { node: '>= 0.4' } + + regexp.prototype.flags@1.5.4: + resolution: + { + integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==, + } + engines: { node: '>= 0.4' } + + resolve-from@4.0.0: + resolution: + { + integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, + } + engines: { node: '>=4' } + + resolve@2.0.0-next.5: + resolution: + { + integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==, + } + hasBin: true + + reusify@1.1.0: + resolution: + { + integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==, + } + engines: { iojs: '>=1.0.0', node: '>=0.10.0' } + + rollup@4.52.2: + resolution: + { + integrity: sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==, + } + engines: { node: '>=18.0.0', npm: '>=8.0.0' } + hasBin: true + + run-parallel@1.2.0: + resolution: + { + integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, + } + + safe-array-concat@1.1.3: + resolution: + { + integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==, + } + engines: { node: '>=0.4' } + + safe-push-apply@1.0.0: + resolution: + { + integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==, + } + engines: { node: '>= 0.4' } + + safe-regex-test@1.1.0: + resolution: + { + integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==, + } + engines: { node: '>= 0.4' } + + scheduler@0.23.2: + resolution: + { + integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==, + } + + semver@6.3.1: + resolution: + { + integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==, + } + hasBin: true + + semver@7.7.2: + resolution: + { + integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==, + } + engines: { node: '>=10' } + hasBin: true + + set-function-length@1.2.2: + resolution: + { + integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==, + } + engines: { node: '>= 0.4' } + + set-function-name@2.0.2: + resolution: + { + integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==, + } + engines: { node: '>= 0.4' } + + set-proto@1.0.0: + resolution: + { + integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==, + } + engines: { node: '>= 0.4' } + + shebang-command@2.0.0: + resolution: + { + integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, + } + engines: { node: '>=8' } + + shebang-regex@3.0.0: + resolution: + { + integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, + } + engines: { node: '>=8' } + + side-channel-list@1.0.0: + resolution: + { + integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==, + } + engines: { node: '>= 0.4' } + + side-channel-map@1.0.1: + resolution: + { + integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==, + } + engines: { node: '>= 0.4' } + + side-channel-weakmap@1.0.2: + resolution: + { + integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==, + } + engines: { node: '>= 0.4' } + + side-channel@1.1.0: + resolution: + { + integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==, + } + engines: { node: '>= 0.4' } + + siginfo@2.0.0: + resolution: + { + integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==, + } + + slash@3.0.0: + resolution: + { + integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==, + } + engines: { node: '>=8' } + + source-map-js@1.2.1: + resolution: + { + integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==, + } + engines: { node: '>=0.10.0' } + + stackback@0.0.2: + resolution: + { + integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==, + } + + std-env@3.9.0: + resolution: + { + integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==, + } + + stop-iteration-iterator@1.1.0: + resolution: + { + integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==, + } + engines: { node: '>= 0.4' } + + string.prototype.matchall@4.0.12: + resolution: + { + integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==, + } + engines: { node: '>= 0.4' } + + string.prototype.repeat@1.0.0: + resolution: + { + integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==, + } + + string.prototype.trim@1.2.10: + resolution: + { + integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==, + } + engines: { node: '>= 0.4' } + + string.prototype.trimend@1.0.9: + resolution: + { + integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==, + } + engines: { node: '>= 0.4' } + + string.prototype.trimstart@1.0.8: + resolution: + { + integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==, + } + engines: { node: '>= 0.4' } + + strip-json-comments@3.1.1: + resolution: + { + integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==, + } + engines: { node: '>=8' } + + strip-literal@3.0.0: + resolution: + { + integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==, + } + + strip-outer@1.0.1: + resolution: + { + integrity: sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==, + } + engines: { node: '>=0.10.0' } + + supports-color@7.2.0: + resolution: + { + integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, + } + engines: { node: '>=8' } + + supports-preserve-symlinks-flag@1.0.0: + resolution: + { + integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, + } + engines: { node: '>= 0.4' } + + tinybench@2.9.0: + resolution: + { + integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==, + } + + tinyexec@0.3.2: + resolution: + { + integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==, + } + + tinyglobby@0.2.15: + resolution: + { + integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==, + } + engines: { node: '>=12.0.0' } + + tinypool@1.1.1: + resolution: + { + integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==, + } + engines: { node: ^18.0.0 || >=20.0.0 } + + tinyrainbow@2.0.0: + resolution: + { + integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==, + } + engines: { node: '>=14.0.0' } + + tinyspy@4.0.4: + resolution: + { + integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==, + } + engines: { node: '>=14.0.0' } + + to-regex-range@5.0.1: + resolution: + { + integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, + } + engines: { node: '>=8.0' } + + trim-repeated@1.0.0: + resolution: + { + integrity: sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==, + } + engines: { node: '>=0.10.0' } + + ts-api-utils@2.1.0: + resolution: + { + integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==, + } + engines: { node: '>=18.12' } + peerDependencies: + typescript: '>=4.8.4' + + tsconfck@3.1.6: + resolution: + { + integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==, + } + engines: { node: ^18 || >=20 } + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + type-check@0.4.0: + resolution: + { + integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, + } + engines: { node: '>= 0.8.0' } + + typed-array-buffer@1.0.3: + resolution: + { + integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==, + } + engines: { node: '>= 0.4' } + + typed-array-byte-length@1.0.3: + resolution: + { + integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==, + } + engines: { node: '>= 0.4' } + + typed-array-byte-offset@1.0.4: + resolution: + { + integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==, + } + engines: { node: '>= 0.4' } + + typed-array-length@1.0.7: + resolution: + { + integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==, + } + engines: { node: '>= 0.4' } + + typescript-eslint@8.44.1: + resolution: + { + integrity: sha512-0ws8uWGrUVTjEeN2OM4K1pLKHK/4NiNP/vz6ns+LjT/6sqpaYzIVFajZb1fj/IDwpsrrHb3Jy0Qm5u9CPcKaeg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.9.2: + resolution: + { + integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==, + } + engines: { node: '>=14.17' } + hasBin: true + + unbox-primitive@1.1.0: + resolution: + { + integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==, + } + engines: { node: '>= 0.4' } + + undici-types@6.21.0: + resolution: + { + integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==, + } + + universalify@2.0.1: + resolution: + { + integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==, + } + engines: { node: '>= 10.0.0' } + + uri-js@4.4.1: + resolution: + { + integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, + } + + vite-node@3.2.4: + resolution: + { + integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==, + } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + hasBin: true + + vite-plugin-mkcert@1.17.8: + resolution: + { + integrity: sha512-S+4tNEyGqdZQ3RLAG54ETeO2qyURHWrVjUWKYikLAbmhh/iJ+36gDEja4OWwFyXNuvyXcZwNt5TZZR9itPeG5Q==, + } + engines: { node: '>=v16.7.0' } + peerDependencies: + vite: '>=3' + + vite-tsconfig-paths@5.1.4: + resolution: + { + integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==, + } + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + + vite@6.3.6: + resolution: + { + integrity: sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==, + } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.4: + resolution: + { + integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==, + } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + which-boxed-primitive@1.1.1: + resolution: + { + integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==, + } + engines: { node: '>= 0.4' } + + which-builtin-type@1.2.1: + resolution: + { + integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==, + } + engines: { node: '>= 0.4' } + + which-collection@1.0.2: + resolution: + { + integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==, + } + engines: { node: '>= 0.4' } + + which-typed-array@1.1.19: + resolution: + { + integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==, + } + engines: { node: '>= 0.4' } + + which@2.0.2: + resolution: + { + integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, + } + engines: { node: '>= 8' } + hasBin: true + + why-is-node-running@2.3.0: + resolution: + { + integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==, + } + engines: { node: '>=8' } + hasBin: true + + word-wrap@1.2.5: + resolution: + { + integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==, + } + engines: { node: '>=0.10.0' } + + yocto-queue@0.1.0: + resolution: + { + integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, + } + engines: { node: '>=10' } + +snapshots: + '@esbuild/aix-ppc64@0.25.10': + optional: true + + '@esbuild/android-arm64@0.25.10': + optional: true + + '@esbuild/android-arm@0.25.10': + optional: true + + '@esbuild/android-x64@0.25.10': + optional: true + + '@esbuild/darwin-arm64@0.25.10': + optional: true + + '@esbuild/darwin-x64@0.25.10': + optional: true + + '@esbuild/freebsd-arm64@0.25.10': + optional: true + + '@esbuild/freebsd-x64@0.25.10': + optional: true + + '@esbuild/linux-arm64@0.25.10': + optional: true + + '@esbuild/linux-arm@0.25.10': + optional: true + + '@esbuild/linux-ia32@0.25.10': + optional: true + + '@esbuild/linux-loong64@0.25.10': + optional: true + + '@esbuild/linux-mips64el@0.25.10': + optional: true + + '@esbuild/linux-ppc64@0.25.10': + optional: true + + '@esbuild/linux-riscv64@0.25.10': + optional: true + + '@esbuild/linux-s390x@0.25.10': + optional: true + + '@esbuild/linux-x64@0.25.10': + optional: true + + '@esbuild/netbsd-arm64@0.25.10': + optional: true + + '@esbuild/netbsd-x64@0.25.10': + optional: true + + '@esbuild/openbsd-arm64@0.25.10': + optional: true + + '@esbuild/openbsd-x64@0.25.10': + optional: true + + '@esbuild/openharmony-arm64@0.25.10': + optional: true + + '@esbuild/sunos-x64@0.25.10': + optional: true + + '@esbuild/win32-arm64@0.25.10': + optional: true + + '@esbuild/win32-ia32@0.25.10': + optional: true + + '@esbuild/win32-x64@0.25.10': + optional: true + + '@eslint-community/eslint-utils@4.9.0(eslint@9.36.0)': + dependencies: + eslint: 9.36.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.21.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.3.1': {} + + '@eslint/core@0.15.2': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.36.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.3.5': + dependencies: + '@eslint/core': 0.15.2 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@remix-run/router@1.23.0': {} + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.52.2': + optional: true + + '@rollup/rollup-android-arm64@4.52.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.52.2': + optional: true + + '@rollup/rollup-darwin-x64@4.52.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.52.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.52.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.52.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.52.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.52.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.52.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.52.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.52.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.52.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.52.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.52.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.52.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.52.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.52.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.52.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.52.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.52.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.52.2': + optional: true + + '@swc/core-darwin-arm64@1.13.19': + optional: true + + '@swc/core-darwin-x64@1.13.19': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.13.19': + optional: true + + '@swc/core-linux-arm64-gnu@1.13.19': + optional: true + + '@swc/core-linux-arm64-musl@1.13.19': + optional: true + + '@swc/core-linux-x64-gnu@1.13.19': + optional: true + + '@swc/core-linux-x64-musl@1.13.19': + optional: true + + '@swc/core-win32-arm64-msvc@1.13.19': + optional: true + + '@swc/core-win32-ia32-msvc@1.13.19': + optional: true + + '@swc/core-win32-x64-msvc@1.13.19': + optional: true + + '@swc/core@1.13.19': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.25 + optionalDependencies: + '@swc/core-darwin-arm64': 1.13.19 + '@swc/core-darwin-x64': 1.13.19 + '@swc/core-linux-arm-gnueabihf': 1.13.19 + '@swc/core-linux-arm64-gnu': 1.13.19 + '@swc/core-linux-arm64-musl': 1.13.19 + '@swc/core-linux-x64-gnu': 1.13.19 + '@swc/core-linux-x64-musl': 1.13.19 + '@swc/core-win32-arm64-msvc': 1.13.19 + '@swc/core-win32-ia32-msvc': 1.13.19 + '@swc/core-win32-x64-msvc': 1.13.19 + + '@swc/counter@0.1.3': {} + + '@swc/types@0.1.25': + dependencies: + '@swc/counter': 0.1.3 + + '@types/chai@5.2.2': + dependencies: + '@types/deep-eql': 4.0.2 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@20.19.17': + dependencies: + undici-types: 6.21.0 + + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.24)': + dependencies: + '@types/react': 18.3.24 + + '@types/react@18.3.24': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.1.3 + + '@typescript-eslint/eslint-plugin@8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0)(typescript@5.9.2))(eslint@9.36.0)(typescript@5.9.2)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.44.1(eslint@9.36.0)(typescript@5.9.2) + '@typescript-eslint/scope-manager': 8.44.1 + '@typescript-eslint/type-utils': 8.44.1(eslint@9.36.0)(typescript@5.9.2) + '@typescript-eslint/utils': 8.44.1(eslint@9.36.0)(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.44.1 + eslint: 9.36.0 + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.44.1(eslint@9.36.0)(typescript@5.9.2)': + dependencies: + '@typescript-eslint/scope-manager': 8.44.1 + '@typescript-eslint/types': 8.44.1 + '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.44.1 + debug: 4.4.3 + eslint: 9.36.0 + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.44.1(typescript@5.9.2)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.44.1(typescript@5.9.2) + '@typescript-eslint/types': 8.44.1 + debug: 4.4.3 + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.44.1': + dependencies: + '@typescript-eslint/types': 8.44.1 + '@typescript-eslint/visitor-keys': 8.44.1 + + '@typescript-eslint/tsconfig-utils@8.44.1(typescript@5.9.2)': + dependencies: + typescript: 5.9.2 + + '@typescript-eslint/type-utils@8.44.1(eslint@9.36.0)(typescript@5.9.2)': + dependencies: + '@typescript-eslint/types': 8.44.1 + '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.9.2) + '@typescript-eslint/utils': 8.44.1(eslint@9.36.0)(typescript@5.9.2) + debug: 4.4.3 + eslint: 9.36.0 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.44.1': {} + + '@typescript-eslint/typescript-estree@8.44.1(typescript@5.9.2)': + dependencies: + '@typescript-eslint/project-service': 8.44.1(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.44.1(typescript@5.9.2) + '@typescript-eslint/types': 8.44.1 + '@typescript-eslint/visitor-keys': 8.44.1 + debug: 4.4.3 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.44.1(eslint@9.36.0)(typescript@5.9.2)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0) + '@typescript-eslint/scope-manager': 8.44.1 + '@typescript-eslint/types': 8.44.1 + '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.9.2) + eslint: 9.36.0 + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.44.1': + dependencies: + '@typescript-eslint/types': 8.44.1 + eslint-visitor-keys: 4.2.1 + + '@vitejs/plugin-react-swc@3.11.0(vite@6.3.6(@types/node@20.19.17))': + dependencies: + '@rolldown/pluginutils': 1.0.0-beta.27 + '@swc/core': 1.13.19 + vite: 6.3.6(@types/node@20.19.17) + transitivePeerDependencies: + - '@swc/helpers' + + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.2 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.4(vite@6.3.6(@types/node@20.19.17))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.19 + optionalDependencies: + vite: 6.3.6(@types/node@20.19.17) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.0.0 + + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.19 + pathe: 2.0.3 + + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array-union@2.1.0: {} + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + assertion-error@2.0.1: {} + + async-function@1.0.0: {} + + async@3.2.6: {} + + asynckit@0.4.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + axios@1.12.2(debug@4.4.3): + dependencies: + follow-redirects: 1.15.11(debug@4.4.3) + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + beamable-sdk@file:../..: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + check-error@2.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@13.1.0: {} + + commondir@1.0.1: {} + + concat-map@0.0.1: {} + + cross-env@7.0.3: + dependencies: + cross-spawn: 7.0.6 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.1.3: {} + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + delayed-stream@1.0.0: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + email-addresses@5.0.0: {} + + es-abstract@1.24.0: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + safe-array-concat: 1.1.3 + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + esbuild@0.25.10: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.10 + '@esbuild/android-arm': 0.25.10 + '@esbuild/android-arm64': 0.25.10 + '@esbuild/android-x64': 0.25.10 + '@esbuild/darwin-arm64': 0.25.10 + '@esbuild/darwin-x64': 0.25.10 + '@esbuild/freebsd-arm64': 0.25.10 + '@esbuild/freebsd-x64': 0.25.10 + '@esbuild/linux-arm': 0.25.10 + '@esbuild/linux-arm64': 0.25.10 + '@esbuild/linux-ia32': 0.25.10 + '@esbuild/linux-loong64': 0.25.10 + '@esbuild/linux-mips64el': 0.25.10 + '@esbuild/linux-ppc64': 0.25.10 + '@esbuild/linux-riscv64': 0.25.10 + '@esbuild/linux-s390x': 0.25.10 + '@esbuild/linux-x64': 0.25.10 + '@esbuild/netbsd-arm64': 0.25.10 + '@esbuild/netbsd-x64': 0.25.10 + '@esbuild/openbsd-arm64': 0.25.10 + '@esbuild/openbsd-x64': 0.25.10 + '@esbuild/openharmony-arm64': 0.25.10 + '@esbuild/sunos-x64': 0.25.10 + '@esbuild/win32-arm64': 0.25.10 + '@esbuild/win32-ia32': 0.25.10 + '@esbuild/win32-x64': 0.25.10 + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@5.2.0(eslint@9.36.0): + dependencies: + eslint: 9.36.0 + + eslint-plugin-react@7.37.5(eslint@9.36.0): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.1 + eslint: 9.36.0 + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.36.0: + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.1 + '@eslint/core': 0.15.2 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.36.0 + '@eslint/plugin-kit': 0.3.5 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + expect-type@1.2.2: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + filename-reserved-regex@2.0.0: {} + + filenamify@4.3.0: + dependencies: + filename-reserved-regex: 2.0.0 + strip-outer: 1.0.1 + trim-repeated: 1.0.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-cache-dir@3.3.2: + dependencies: + commondir: 1.0.1 + make-dir: 3.1.0 + pkg-dir: 4.2.0 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + follow-redirects@1.15.11(debug@4.4.3): + optionalDependencies: + debug: 4.4.3 + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fs-extra@11.3.2: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + get-intrinsic@1.3.0: + 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 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + gh-pages@6.3.0: + dependencies: + async: 3.2.6 + commander: 13.1.0 + email-addresses: 5.0.0 + filenamify: 4.3.0 + find-cache-dir: 3.3.2 + fs-extra: 11.3.2 + globby: 11.1.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@15.15.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + globrex@0.1.2: {} + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + + js-tokens@4.0.0: {} + + js-tokens@9.0.1: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + loupe@3.2.1: {} + + magic-string@0.30.19: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + make-dir@3.1.0: + dependencies: + semver: 6.3.1 + + math-intrinsics@1.1.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-try@2.2.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-type@4.0.0: {} + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + possible-typed-array-names@1.1.0: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier@3.6.2: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + proxy-from-env@1.1.0: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-is@16.13.1: {} + + react-router-dom@6.30.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@remix-run/router': 1.23.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 6.30.1(react@18.3.1) + + react-router@6.30.1(react@18.3.1): + dependencies: + '@remix-run/router': 1.23.0 + react: 18.3.1 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + resolve-from@4.0.0: {} + + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rollup@4.52.2: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.52.2 + '@rollup/rollup-android-arm64': 4.52.2 + '@rollup/rollup-darwin-arm64': 4.52.2 + '@rollup/rollup-darwin-x64': 4.52.2 + '@rollup/rollup-freebsd-arm64': 4.52.2 + '@rollup/rollup-freebsd-x64': 4.52.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.52.2 + '@rollup/rollup-linux-arm-musleabihf': 4.52.2 + '@rollup/rollup-linux-arm64-gnu': 4.52.2 + '@rollup/rollup-linux-arm64-musl': 4.52.2 + '@rollup/rollup-linux-loong64-gnu': 4.52.2 + '@rollup/rollup-linux-ppc64-gnu': 4.52.2 + '@rollup/rollup-linux-riscv64-gnu': 4.52.2 + '@rollup/rollup-linux-riscv64-musl': 4.52.2 + '@rollup/rollup-linux-s390x-gnu': 4.52.2 + '@rollup/rollup-linux-x64-gnu': 4.52.2 + '@rollup/rollup-linux-x64-musl': 4.52.2 + '@rollup/rollup-openharmony-arm64': 4.52.2 + '@rollup/rollup-win32-arm64-msvc': 4.52.2 + '@rollup/rollup-win32-ia32-msvc': 4.52.2 + '@rollup/rollup-win32-x64-gnu': 4.52.2 + '@rollup/rollup-win32-x64-msvc': 4.52.2 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + semver@6.3.1: {} + + semver@7.7.2: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + + slash@3.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@3.9.0: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.0 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + strip-json-comments@3.1.1: {} + + strip-literal@3.0.0: + dependencies: + js-tokens: 9.0.1 + + strip-outer@1.0.1: + dependencies: + escape-string-regexp: 1.0.5 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.4: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + trim-repeated@1.0.0: + dependencies: + escape-string-regexp: 1.0.5 + + ts-api-utils@2.1.0(typescript@5.9.2): + dependencies: + typescript: 5.9.2 + + tsconfck@3.1.6(typescript@5.9.2): + optionalDependencies: + typescript: 5.9.2 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript-eslint@8.44.1(eslint@9.36.0)(typescript@5.9.2): + dependencies: + '@typescript-eslint/eslint-plugin': 8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0)(typescript@5.9.2))(eslint@9.36.0)(typescript@5.9.2) + '@typescript-eslint/parser': 8.44.1(eslint@9.36.0)(typescript@5.9.2) + '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.9.2) + '@typescript-eslint/utils': 8.44.1(eslint@9.36.0)(typescript@5.9.2) + eslint: 9.36.0 + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + typescript@5.9.2: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@6.21.0: {} + + universalify@2.0.1: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vite-node@3.2.4(@types/node@20.19.17): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.3.6(@types/node@20.19.17) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite-plugin-mkcert@1.17.8(vite@6.3.6(@types/node@20.19.17)): + dependencies: + axios: 1.12.2(debug@4.4.3) + debug: 4.4.3 + picocolors: 1.1.1 + vite: 6.3.6(@types/node@20.19.17) + transitivePeerDependencies: + - supports-color + + vite-tsconfig-paths@5.1.4(typescript@5.9.2)(vite@6.3.6(@types/node@20.19.17)): + dependencies: + debug: 4.4.3 + globrex: 0.1.2 + tsconfck: 3.1.6(typescript@5.9.2) + optionalDependencies: + vite: 6.3.6(@types/node@20.19.17) + transitivePeerDependencies: + - supports-color + - typescript + + vite@6.3.6(@types/node@20.19.17): + dependencies: + esbuild: 0.25.10 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.52.2 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 20.19.17 + fsevents: 2.3.3 + + vitest@3.2.4(@types/node@20.19.17): + dependencies: + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@6.3.6(@types/node@20.19.17)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.2.2 + magic-string: 0.30.19 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 6.3.6(@types/node@20.19.17) + vite-node: 3.2.4(@types/node@20.19.17) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.19.17 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.0 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + yocto-queue@0.1.0: {} diff --git a/web/samples/WordWiz/src/assets/EndlessIcon.tsx b/web/samples/WordWiz/src/assets/EndlessIcon.tsx new file mode 100644 index 0000000000..c018bcf9cd --- /dev/null +++ b/web/samples/WordWiz/src/assets/EndlessIcon.tsx @@ -0,0 +1,25 @@ +export default function EndlessIcon() { + return ( + + + + + + + + + + + + ); +} diff --git a/web/samples/WordWiz/src/assets/HomeIcon.tsx b/web/samples/WordWiz/src/assets/HomeIcon.tsx new file mode 100644 index 0000000000..7d7d4058ad --- /dev/null +++ b/web/samples/WordWiz/src/assets/HomeIcon.tsx @@ -0,0 +1,18 @@ +export default function HomeIcon() { + return ( + + + + + ); +} diff --git a/web/samples/WordWiz/src/assets/NextIcon.tsx b/web/samples/WordWiz/src/assets/NextIcon.tsx new file mode 100644 index 0000000000..3c1740aa2f --- /dev/null +++ b/web/samples/WordWiz/src/assets/NextIcon.tsx @@ -0,0 +1,19 @@ +export default function NextIcon() { + return ( + + + + + + ); +} diff --git a/web/samples/WordWiz/src/assets/allowed.txt b/web/samples/WordWiz/src/assets/allowed.txt new file mode 100644 index 0000000000..cc443a0688 --- /dev/null +++ b/web/samples/WordWiz/src/assets/allowed.txt @@ -0,0 +1,10657 @@ +aahed +aalii +aargh +aarti +abaca +abaci +abacs +abaft +abaka +abamp +aband +abash +abask +abaya +abbas +abbed +abbes +abcee +abeam +abear +abele +abers +abets +abies +abler +ables +ablet +ablow +abmho +abohm +aboil +aboma +aboon +abord +abore +abram +abray +abrim +abrin +abris +absey +absit +abuna +abune +abuts +abuzz +abyes +abysm +acais +acari +accas +accoy +acerb +acers +aceta +achar +ached +aches +achoo +acids +acidy +acing +acini +ackee +acker +acmes +acmic +acned +acnes +acock +acold +acred +acres +acros +acted +actin +acton +acyls +adaws +adays +adbot +addax +added +adder +addio +addle +adeem +adhan +adieu +adios +adits +adman +admen +admix +adobo +adown +adoze +adrad +adred +adsum +aduki +adunc +adust +advew +adyta +adzed +adzes +aecia +aedes +aegis +aeons +aerie +aeros +aesir +afald +afara +afars +afear +aflaj +afore +afrit +afros +agama +agami +agars +agast +agave +agaze +agene +agers +agger +aggie +aggri +aggro +aggry +aghas +agila +agios +agism +agist +agita +aglee +aglet +agley +agloo +aglus +agmas +agoge +agone +agons +agood +agria +agrin +agros +agued +agues +aguna +aguti +aheap +ahent +ahigh +ahind +ahing +ahint +ahold +ahull +ahuru +aidas +aided +aides +aidoi +aidos +aiery +aigas +aight +ailed +aimed +aimer +ainee +ainga +aioli +aired +airer +airns +airth +airts +aitch +aitus +aiver +aiyee +aizle +ajies +ajiva +ajuga +ajwan +akees +akela +akene +aking +akita +akkas +alaap +alack +alamo +aland +alane +alang +alans +alant +alapa +alaps +alary +alate +alays +albas +albee +alcid +alcos +aldea +alder +aldol +aleck +alecs +alefs +aleft +aleph +alews +aleye +alfas +algal +algas +algid +algin +algor +algum +alias +alifs +aline +alist +aliya +alkie +alkos +alkyd +alkyl +allee +allel +allis +allod +allyl +almah +almas +almeh +almes +almud +almug +alods +aloed +aloes +aloha +aloin +aloos +alowe +altho +altos +alula +alums +alure +alvar +alway +amahs +amain +amate +amaut +amban +ambit +ambos +ambry +ameba +ameer +amene +amens +ament +amias +amice +amici +amide +amido +amids +amies +amiga +amigo +amine +amino +amins +amirs +amlas +amman +ammon +ammos +amnia +amnic +amnio +amoks +amole +amort +amour +amove +amowt +amped +ampul +amrit +amuck +amyls +anana +anata +ancho +ancle +ancon +andro +anear +anele +anent +angas +anglo +anigh +anile +anils +anima +animi +anion +anise +anker +ankhs +ankus +anlas +annal +annas +annat +anoas +anole +anomy +ansae +antae +antar +antas +anted +antes +antis +antra +antre +antsy +anura +anyon +apace +apage +apaid +apayd +apays +apeak +apeek +apers +apert +apery +apgar +aphis +apian +apiol +apish +apism +apode +apods +apoop +aport +appal +appay +appel +appro +appui +appuy +apres +apses +apsis +apsos +apted +apter +aquae +aquas +araba +araks +arame +arars +arbas +arced +archi +arcos +arcus +ardeb +ardri +aread +areae +areal +arear +areas +areca +aredd +arede +arefy +areic +arene +arepa +arere +arete +arets +arett +argal +argan +argil +argle +argol +argon +argot +argus +arhat +arias +ariel +ariki +arils +ariot +arish +arked +arled +arles +armed +armer +armet +armil +arnas +arnut +aroba +aroha +aroid +arpas +arpen +arrah +arras +arret +arris +arroz +arsed +arses +arsey +arsis +artal +artel +artic +artis +aruhe +arums +arval +arvee +arvos +aryls +asana +ascon +ascus +asdic +ashed +ashes +ashet +asked +asker +askoi +askos +aspen +asper +aspic +aspie +aspis +aspro +assai +assam +asses +assez +assot +aster +astir +astun +asura +asway +aswim +asyla +ataps +ataxy +atigi +atilt +atimy +atlas +atman +atmas +atmos +atocs +atoke +atoks +atoms +atomy +atony +atopy +atria +atrip +attap +attar +atuas +audad +auger +aught +aulas +aulic +auloi +aulos +aumil +aunes +aunts +aurae +aural +aurar +auras +aurei +aures +auric +auris +aurum +autos +auxin +avale +avant +avast +avels +avens +avers +avgas +avine +avion +avise +aviso +avize +avows +avyze +awarn +awato +awave +aways +awdls +aweel +aweto +awing +awmry +awned +awner +awols +awork +axels +axile +axils +axing +axite +axled +axles +axman +axmen +axoid +axone +axons +ayahs +ayaya +ayelp +aygre +ayins +ayont +ayres +ayrie +azans +azide +azido +azine +azlon +azoic +azole +azons +azote +azoth +azuki +azurn +azury +azygy +azyme +azyms +baaed +baals +babas +babel +babes +babka +baboo +babul +babus +bacca +bacco +baccy +bacha +bachs +backs +baddy +baels +baffs +baffy +bafts +baghs +bagie +bahts +bahus +bahut +bails +bairn +baisa +baith +baits +baiza +baize +bajan +bajra +bajri +bajus +baked +baken +bakes +bakra +balas +balds +baldy +baled +bales +balks +balky +balls +bally +balms +baloo +balsa +balti +balun +balus +bambi +banak +banco +bancs +banda +bandh +bands +bandy +baned +banes +bangs +bania +banks +banns +bants +bantu +banty +banya +bapus +barbe +barbs +barby +barca +barde +bardo +bards +bardy +bared +barer +bares +barfi +barfs +baric +barks +barky +barms +barmy +barns +barny +barps +barra +barre +barro +barry +barye +basan +based +basen +baser +bases +basho +basij +basks +bason +basse +bassi +basso +bassy +basta +basti +basto +basts +bated +bates +baths +batik +batta +batts +battu +bauds +bauks +baulk +baurs +bavin +bawds +bawks +bawls +bawns +bawrs +bawty +bayed +bayer +bayes +bayle +bayts +bazar +bazoo +beads +beaks +beaky +beals +beams +beamy +beano +beans +beany +beare +bears +beath +beats +beaty +beaus +beaut +beaux +bebop +becap +becke +becks +bedad +bedel +bedes +bedew +bedim +bedye +beedi +beefs +beeps +beers +beery +beets +befog +begad +begar +begem +begot +begum +beige +beigy +beins +bekah +belah +belar +belay +belee +belga +bells +belon +belts +bemad +bemas +bemix +bemud +bends +bendy +benes +benet +benga +benis +benne +benni +benny +bento +bents +benty +bepat +beray +beres +bergs +berko +berks +berme +berms +berob +beryl +besat +besaw +besee +beses +besit +besom +besot +besti +bests +betas +beted +betes +beths +betid +beton +betta +betty +bever +bevor +bevue +bevvy +bewet +bewig +bezes +bezil +bezzy +bhais +bhaji +bhang +bhats +bhels +bhoot +bhuna +bhuts +biach +biali +bialy +bibbs +bibes +biccy +bices +bided +bider +bides +bidet +bidis +bidon +bield +biers +biffo +biffs +biffy +bifid +bigae +biggs +biggy +bigha +bight +bigly +bigos +bijou +biked +biker +bikes +bikie +bilbo +bilby +biled +biles +bilgy +bilks +bills +bimah +bimas +bimbo +binal +bindi +binds +biner +bines +bings +bingy +binit +binks +bints +biogs +biont +biota +biped +bipod +birds +birks +birle +birls +biros +birrs +birse +birsy +bises +bisks +bisom +bitch +biter +bites +bitos +bitou +bitsy +bitte +bitts +bivia +bivvy +bizes +bizzo +bizzy +blabs +blads +blady +blaer +blaes +blaff +blags +blahs +blain +blams +blart +blase +blash +blate +blats +blatt +blaud +blawn +blaws +blays +blear +blebs +blech +blees +blent +blert +blest +blets +bleys +blimy +bling +blini +blins +bliny +blips +blist +blite +blits +blive +blobs +blocs +blogs +blook +bloop +blore +blots +blows +blowy +blubs +blude +bluds +bludy +blued +blues +bluet +bluey +bluid +blume +blunk +blurs +blype +boabs +boaks +boars +boart +boats +bobac +bobak +bobas +bobol +bobos +bocca +bocce +bocci +boche +bocks +boded +bodes +bodge +bodhi +bodle +boeps +boets +boeuf +boffo +boffs +bogan +bogey +boggy +bogie +bogle +bogue +bogus +bohea +bohos +boils +boing +boink +boite +boked +bokeh +bokes +bokos +bolar +bolas +bolds +boles +bolix +bolls +bolos +bolts +bolus +bomas +bombe +bombo +bombs +bonce +bonds +boned +boner +bones +bongs +bonie +bonks +bonne +bonny +bonza +bonze +booai +booay +boobs +boody +booed +boofy +boogy +boohs +books +booky +bools +booms +boomy +boong +boons +boord +boors +boose +boots +boppy +borak +boral +boras +borde +bords +bored +boree +borel +borer +bores +borgo +boric +borks +borms +borna +boron +borts +borty +bortz +bosie +bosks +bosky +boson +bosun +botas +botel +botes +bothy +botte +botts +botty +bouge +bouks +boult +bouns +bourd +bourg +bourn +bouse +bousy +bouts +bovid +bowat +bowed +bower +bowes +bowet +bowie +bowls +bowne +bowrs +bowse +boxed +boxen +boxes +boxla +boxty +boyar +boyau +boyed +boyfs +boygs +boyla +boyos +boysy +bozos +braai +brach +brack +bract +brads +braes +brags +brail +braks +braky +brame +brane +brank +brans +brant +brast +brats +brava +bravi +braws +braxy +brays +braza +braze +bream +brede +breds +breem +breer +brees +breid +breis +breme +brens +brent +brere +brers +breve +brews +breys +brier +bries +brigs +briki +briks +brill +brims +brins +brios +brise +briss +brith +brits +britt +brize +broch +brock +brods +brogh +brogs +brome +bromo +bronc +brond +brool +broos +brose +brosy +brows +brugh +bruin +bruit +brule +brume +brung +brusk +brust +bruts +buats +buaze +bubal +bubas +bubba +bubbe +bubby +bubus +buchu +bucko +bucks +bucku +budas +budis +budos +buffa +buffe +buffi +buffo +buffs +buffy +bufos +bufty +buhls +buhrs +buiks +buist +bukes +bulbs +bulgy +bulks +bulla +bulls +bulse +bumbo +bumfs +bumph +bumps +bumpy +bunas +bunce +bunco +bunde +bundh +bunds +bundt +bundu +bundy +bungs +bungy +bunia +bunje +bunjy +bunko +bunks +bunns +bunts +bunty +bunya +buoys +buppy +buran +buras +burbs +burds +buret +burfi +burgh +burgs +burin +burka +burke +burks +burls +burns +buroo +burps +burqa +burro +burrs +burry +bursa +burse +busby +buses +busks +busky +bussu +busti +busts +busty +buteo +butes +butle +butoh +butts +butty +butut +butyl +buzzy +bwana +bwazi +byded +bydes +byked +bykes +byres +byrls +byssi +bytes +byway +caaed +cabas +caber +cabob +caboc +cabre +cacas +cacks +cacky +cadee +cades +cadge +cadgy +cadie +cadis +cadre +caeca +caese +cafes +caffs +caged +cager +cages +cagot +cahow +caids +cains +caird +cajon +cajun +caked +cakes +cakey +calfs +calid +calif +calix +calks +calla +calls +calms +calmy +calos +calpa +calps +calve +calyx +caman +camas +cames +camis +camos +campi +campo +camps +campy +camus +caned +caneh +caner +canes +cangs +canid +canna +canns +canso +canst +canto +cants +canty +capas +caped +capes +capex +caphs +capiz +caple +capon +capos +capot +capri +capul +carap +carbo +carbs +carby +cardi +cards +cardy +cared +carer +cares +caret +carex +carks +carle +carls +carns +carny +carob +carom +caron +carpi +carps +carrs +carse +carta +carte +carts +carvy +casas +casco +cased +cases +casks +casky +casts +casus +cates +cauda +cauks +cauld +cauls +caums +caups +cauri +causa +cavas +caved +cavel +caver +caves +cavie +cawed +cawks +caxon +ceaze +cebid +cecal +cecum +ceded +ceder +cedes +cedis +ceiba +ceili +ceils +celeb +cella +celli +cells +celom +celts +cense +cento +cents +centu +ceorl +cepes +cerci +cered +ceres +cerge +ceria +ceric +cerne +ceroc +ceros +certs +certy +cesse +cesta +cesti +cetes +cetyl +cezve +chace +chack +chaco +chado +chads +chaft +chais +chals +chams +chana +chang +chank +chape +chaps +chapt +chara +chare +chark +charr +chars +chary +chats +chave +chavs +chawk +chaws +chaya +chays +cheep +chefs +cheka +chela +chelp +chemo +chems +chere +chert +cheth +chevy +chews +chewy +chiao +chias +chibs +chica +chich +chico +chics +chiel +chiks +chile +chimb +chimo +chimp +chine +ching +chink +chino +chins +chips +chirk +chirl +chirm +chiro +chirr +chirt +chiru +chits +chive +chivs +chivy +chizz +choco +chocs +chode +chogs +choil +choko +choky +chola +choli +cholo +chomp +chons +choof +chook +choom +choon +chops +chota +chott +chout +choux +chowk +chows +chubs +chufa +chuff +chugs +chums +churl +churr +chuse +chuts +chyle +chyme +chynd +cibol +cided +cides +ciels +ciggy +cilia +cills +cimar +cimex +cinct +cines +cinqs +cions +cippi +circs +cires +cirls +cirri +cisco +cissy +cists +cital +cited +citer +cites +cives +civet +civie +civvy +clach +clade +clads +claes +clags +clame +clams +clans +claps +clapt +claro +clart +clary +clast +clats +claut +clave +clavi +claws +clays +cleck +cleek +cleep +clefs +clegs +cleik +clems +clepe +clept +cleve +clews +clied +clies +clift +clime +cline +clint +clipe +clips +clipt +clits +cloam +clods +cloff +clogs +cloke +clomb +clomp +clonk +clons +cloop +cloot +clops +clote +clots +clour +clous +clows +cloye +cloys +cloze +clubs +clues +cluey +clunk +clype +cnida +coact +coady +coala +coals +coaly +coapt +coarb +coate +coati +coats +cobbs +cobby +cobia +coble +cobza +cocas +cocci +cocco +cocks +cocky +cocos +codas +codec +coded +coden +coder +codes +codex +codon +coeds +coffs +cogie +cogon +cogue +cohab +cohen +cohoe +cohog +cohos +coifs +coign +coils +coins +coirs +coits +coked +cokes +colas +colby +colds +coled +coles +coley +colic +colin +colls +colly +colog +colts +colza +comae +comal +comas +combe +combi +combo +combs +comby +comer +comes +comix +commo +comms +commy +compo +comps +compt +comte +comus +coned +cones +coney +confs +conga +conge +congo +conia +conin +conks +conky +conne +conns +conte +conto +conus +convo +cooch +cooed +cooee +cooer +cooey +coofs +cooks +cooky +cools +cooly +coomb +cooms +coomy +coons +coops +coopt +coost +coots +cooze +copal +copay +coped +copen +coper +copes +coppy +copra +copsy +coqui +coram +corbe +corby +cords +cored +cores +corey +corgi +coria +corks +corky +corms +corni +corno +corns +cornu +corps +corse +corso +cosec +cosed +coses +coset +cosey +cosie +costa +coste +costs +cotan +coted +cotes +coths +cotta +cotts +coude +coups +courb +courd +coure +cours +couta +couth +coved +coves +covin +cowal +cowan +cowed +cowks +cowls +cowps +cowry +coxae +coxal +coxed +coxes +coxib +coyau +coyed +coyer +coypu +cozed +cozen +cozes +cozey +cozie +craal +crabs +crags +craic +craig +crake +crame +crams +crans +crape +craps +crapy +crare +craws +crays +creds +creel +crees +crems +crena +creps +crepy +crewe +crews +crias +cribs +cries +crims +crine +crios +cripe +crips +crise +crith +crits +croci +crocs +croft +crogs +cromb +crome +cronk +crons +crool +croon +crops +crore +crost +crout +crows +croze +cruck +crudo +cruds +crudy +crues +cruet +cruft +crunk +cruor +crura +cruse +crusy +cruve +crwth +cryer +ctene +cubby +cubeb +cubed +cuber +cubes +cubit +cuddy +cuffo +cuffs +cuifs +cuing +cuish +cuits +cukes +culch +culet +culex +culls +cully +culms +culpa +culti +cults +culty +cumec +cundy +cunei +cunit +cunts +cupel +cupid +cuppa +cuppy +curat +curbs +curch +curds +curdy +cured +curer +cures +curet +curfs +curia +curie +curli +curls +curns +curny +currs +cursi +curst +cusec +cushy +cusks +cusps +cuspy +cusso +cusum +cutch +cuter +cutes +cutey +cutin +cutis +cutto +cutty +cutup +cuvee +cuzes +cwtch +cyano +cyans +cycad +cycas +cyclo +cyder +cylix +cymae +cymar +cymas +cymes +cymol +cysts +cytes +cyton +czars +daals +dabba +daces +dacha +dacks +dadah +dadas +dados +daffs +daffy +dagga +daggy +dagos +dahls +daiko +daine +daint +daker +daled +dales +dalis +dalle +dalts +daman +damar +dames +damme +damns +damps +dampy +dancy +dangs +danio +danks +danny +dants +daraf +darbs +darcy +dared +darer +dares +darga +dargs +daric +daris +darks +darky +darns +darre +darts +darzi +dashi +dashy +datal +dated +dater +dates +datos +datto +daube +daubs +dauby +dauds +dault +daurs +dauts +daven +davit +dawah +dawds +dawed +dawen +dawks +dawns +dawts +dayan +daych +daynt +dazed +dazer +dazes +deads +deair +deals +deans +deare +dearn +dears +deary +deash +deave +deaws +deawy +debag +debby +debel +debes +debts +debud +debur +debus +debye +decad +decaf +decan +decko +decks +decos +dedal +deeds +deedy +deely +deems +deens +deeps +deere +deers +deets +deeve +deevs +defat +deffo +defis +defog +degas +degum +degus +deice +deids +deify +deils +deism +deist +deked +dekes +dekko +deled +deles +delfs +delft +delis +dells +delly +delos +delph +delts +deman +demes +demic +demit +demob +demoi +demos +dempt +denar +denay +dench +denes +denet +denis +dents +deoxy +derat +deray +dered +deres +derig +derma +derms +derns +derny +deros +derro +derry +derth +dervs +desex +deshi +desis +desks +desse +devas +devel +devis +devon +devos +devot +dewan +dewar +dewax +dewed +dexes +dexie +dhaba +dhaks +dhals +dhikr +dhobi +dhole +dholl +dhols +dhoti +dhows +dhuti +diact +dials +diane +diazo +dibbs +diced +dicer +dices +dicht +dicks +dicky +dicot +dicta +dicts +dicty +diddy +didie +didos +didst +diebs +diels +diene +diets +diffs +dight +dikas +diked +diker +dikes +dikey +dildo +dilli +dills +dimbo +dimer +dimes +dimps +dinar +dined +dines +dinge +dings +dinic +dinks +dinky +dinna +dinos +dints +diols +diota +dippy +dipso +diram +direr +dirke +dirks +dirls +dirts +disas +disci +discs +dishy +disks +disme +dital +ditas +dited +dites +ditsy +ditts +ditzy +divan +divas +dived +dives +divis +divna +divos +divot +divvy +diwan +dixie +dixit +diyas +dizen +djinn +djins +doabs +doats +dobby +dobes +dobie +dobla +dobra +dobro +docht +docks +docos +docus +doddy +dodos +doeks +doers +doest +doeth +doffs +dogan +doges +dogey +doggo +doggy +dogie +dohyo +doilt +doily +doits +dojos +dolce +dolci +doled +doles +dolia +dolls +dolma +dolor +dolos +dolts +domal +domed +domes +domic +donah +donas +donee +doner +donga +dongs +donko +donna +donne +donny +donsy +doobs +dooce +doody +dooks +doole +dools +dooly +dooms +doomy +doona +doorn +doors +doozy +dopas +doped +doper +dopes +dorad +dorba +dorbs +doree +dores +doric +doris +dorks +dorky +dorms +dormy +dorps +dorrs +dorsa +dorse +dorts +dorty +dosai +dosas +dosed +doseh +doser +doses +dosha +dotal +doted +doter +dotes +dotty +douar +douce +doucs +douks +doula +douma +doums +doups +doura +douse +douts +doved +doven +dover +doves +dovie +dowar +dowds +dowed +dower +dowie +dowle +dowls +dowly +downa +downs +dowps +dowse +dowts +doxed +doxes +doxie +doyen +doyly +dozed +dozer +dozes +drabs +drack +draco +draff +drags +drail +drams +drant +draps +drats +drave +draws +drays +drear +dreck +dreed +dreer +drees +dregs +dreks +drent +drere +drest +dreys +dribs +drice +dries +drily +drips +dript +droid +droil +droke +drole +drome +drony +droob +droog +drook +drops +dropt +drouk +drows +drubs +drugs +drums +drupe +druse +drusy +druxy +dryad +dryas +dsobo +dsomo +duads +duals +duans +duars +dubbo +ducal +ducat +duces +ducks +ducky +ducts +duddy +duded +dudes +duels +duets +duett +duffs +dufus +duing +duits +dukas +duked +dukes +dukka +dulce +dules +dulia +dulls +dulse +dumas +dumbo +dumbs +dumka +dumky +dumps +dunam +dunch +dunes +dungs +dungy +dunks +dunno +dunny +dunsh +dunts +duomi +duomo +duped +duper +dupes +duple +duply +duppy +dural +duras +dured +dures +durgy +durns +duroc +duros +duroy +durra +durrs +durry +durst +durum +durzi +dusks +dusts +duxes +dwaal +dwale +dwalm +dwams +dwang +dwaum +dweeb +dwile +dwine +dyads +dyers +dyked +dykes +dykey +dykon +dynel +dynes +dzhos +eagre +ealed +eales +eaned +eards +eared +earls +earns +earnt +earst +eased +easer +eases +easle +easts +eathe +eaved +eaves +ebbed +ebbet +ebons +ebook +ecads +eched +eches +echos +ecrus +edema +edged +edger +edges +edile +edits +educe +educt +eejit +eensy +eeven +eevns +effed +egads +egers +egest +eggar +egged +egger +egmas +ehing +eider +eidos +eigne +eiked +eikon +eilds +eisel +ejido +ekkas +elain +eland +elans +elchi +eldin +elemi +elfed +eliad +elint +elmen +eloge +elogy +eloin +elops +elpee +elsin +elute +elvan +elven +elver +elves +emacs +embar +embay +embog +embow +embox +embus +emeer +emend +emerg +emery +emeus +emics +emirs +emits +emmas +emmer +emmet +emmew +emmys +emoji +emong +emote +emove +empts +emule +emure +emyde +emyds +enarm +enate +ended +ender +endew +endue +enews +enfix +eniac +enlit +enmew +ennog +enoki +enols +enorm +enows +enrol +ensew +ensky +entia +enure +enurn +envoi +enzym +eorls +eosin +epact +epees +ephah +ephas +ephod +ephor +epics +epode +epopt +epris +eques +equid +erbia +erevs +ergon +ergos +ergot +erhus +erica +erick +erics +ering +erned +ernes +erose +erred +erses +eruct +erugo +eruvs +erven +ervil +escar +escot +esile +eskar +esker +esnes +esses +estoc +estop +estro +etage +etape +etats +etens +ethal +ethne +ethyl +etics +etnas +ettin +ettle +etuis +etwee +etyma +eughs +euked +eupad +euros +eusol +evens +evert +evets +evhoe +evils +evite +evohe +ewers +ewest +ewhow +ewked +exams +exeat +execs +exeem +exeme +exfil +exies +exine +exing +exits +exode +exome +exons +expat +expos +exude +exuls +exurb +eyass +eyers +eyots +eyras +eyres +eyrie +eyrir +ezine +fabby +faced +facer +faces +facia +facta +facts +faddy +faded +fader +fades +fadge +fados +faena +faery +faffs +faffy +faggy +fagin +fagot +faiks +fails +faine +fains +fairs +faked +faker +fakes +fakey +fakie +fakir +falaj +falls +famed +fames +fanal +fands +fanes +fanga +fango +fangs +fanks +fanon +fanos +fanum +faqir +farad +farci +farcy +fards +fared +farer +fares +farle +farls +farms +faros +farro +farse +farts +fasci +fasti +fasts +fated +fates +fatly +fatso +fatwa +faugh +fauld +fauns +faurd +fauts +fauve +favas +favel +faver +faves +favus +fawns +fawny +faxed +faxes +fayed +fayer +fayne +fayre +fazed +fazes +feals +feare +fears +feart +fease +feats +feaze +feces +fecht +fecit +fecks +fedex +feebs +feeds +feels +feens +feers +feese +feeze +fehme +feint +feist +felch +felid +fells +felly +felts +felty +femal +femes +femmy +fends +fendy +fenis +fenks +fenny +fents +feods +feoff +ferer +feres +feria +ferly +fermi +ferms +ferns +ferny +fesse +festa +fests +festy +fetas +feted +fetes +fetor +fetta +fetts +fetwa +feuar +feuds +feued +feyed +feyer +feyly +fezes +fezzy +fiars +fiats +fibro +fices +fiche +fichu +ficin +ficos +fides +fidge +fidos +fiefs +fient +fiere +fiers +fiest +fifed +fifer +fifes +fifis +figgy +figos +fiked +fikes +filar +filch +filed +files +filii +filks +fille +fillo +fills +filmi +films +filos +filum +finca +finds +fined +fines +finis +finks +finny +finos +fiord +fiqhs +fique +fired +firer +fires +firie +firks +firms +firns +firry +firth +fiscs +fisks +fists +fisty +fitch +fitly +fitna +fitte +fitts +fiver +fives +fixed +fixes +fixit +fjeld +flabs +flaff +flags +flaks +flamm +flams +flamy +flane +flans +flaps +flary +flats +flava +flawn +flaws +flawy +flaxy +flays +fleam +fleas +fleek +fleer +flees +flegs +fleme +fleur +flews +flexi +flexo +fleys +flics +flied +flies +flimp +flims +flips +flirs +flisk +flite +flits +flitt +flobs +flocs +floes +flogs +flong +flops +flors +flory +flosh +flota +flote +flows +flubs +flued +flues +fluey +fluky +flump +fluor +flurr +fluty +fluyt +flyby +flype +flyte +foals +foams +foehn +fogey +fogie +fogle +fogou +fohns +foids +foils +foins +folds +foley +folia +folic +folie +folks +folky +fomes +fonda +fonds +fondu +fones +fonly +fonts +foods +foody +fools +foots +footy +foram +forbs +forby +fordo +fords +forel +fores +forex +forks +forky +forme +forms +forts +forza +forze +fossa +fosse +fouat +fouds +fouer +fouet +foule +fouls +fount +fours +fouth +fovea +fowls +fowth +foxed +foxes +foxie +foyle +foyne +frabs +frack +fract +frags +fraim +franc +frape +fraps +frass +frate +frati +frats +fraus +frays +frees +freet +freit +fremd +frena +freon +frere +frets +fribs +frier +fries +frigs +frise +frist +frith +frits +fritt +frize +frizz +froes +frogs +frons +frore +frorn +frory +frosh +frows +frowy +frugs +frump +frush +frust +fryer +fubar +fubby +fubsy +fucks +fucus +fuddy +fudgy +fuels +fuero +fuffs +fuffy +fugal +fuggy +fugie +fugio +fugle +fugly +fugus +fujis +fulls +fumed +fumer +fumes +fumet +fundi +funds +fundy +fungo +fungs +funks +fural +furan +furca +furls +furol +furrs +furth +furze +furzy +fused +fusee +fusel +fuses +fusil +fusks +fusts +fusty +futon +fuzed +fuzee +fuzes +fuzil +fyces +fyked +fykes +fyles +fyrds +fytte +gabba +gabby +gable +gaddi +gades +gadge +gadid +gadis +gadje +gadjo +gadso +gaffs +gaged +gager +gages +gaids +gains +gairs +gaita +gaits +gaitt +gajos +galah +galas +galax +galea +galed +gales +galls +gally +galop +galut +galvo +gamas +gamay +gamba +gambe +gambo +gambs +gamed +games +gamey +gamic +gamin +gamme +gammy +gamps +ganch +gandy +ganef +ganev +gangs +ganja +ganof +gants +gaols +gaped +gaper +gapes +gapos +gappy +garbe +garbo +garbs +garda +gares +garis +garms +garni +garre +garth +garum +gases +gasps +gaspy +gasts +gatch +gated +gater +gates +gaths +gator +gauch +gaucy +gauds +gauje +gault +gaums +gaumy +gaups +gaurs +gauss +gauzy +gavot +gawcy +gawds +gawks +gawps +gawsy +gayal +gazal +gazar +gazed +gazes +gazon +gazoo +geals +geans +geare +gears +geats +gebur +gecks +geeks +geeps +geest +geist +geits +gelds +gelee +gelid +gelly +gelts +gemel +gemma +gemmy +gemot +genal +genas +genes +genet +genic +genii +genip +genny +genoa +genom +genro +gents +genty +genua +genus +geode +geoid +gerah +gerbe +geres +gerle +germs +germy +gerne +gesse +gesso +geste +gests +getas +getup +geums +geyan +geyer +ghast +ghats +ghaut +ghazi +ghees +ghest +ghyll +gibed +gibel +giber +gibes +gibli +gibus +gifts +gigas +gighe +gigot +gigue +gilas +gilds +gilet +gills +gilly +gilpy +gilts +gimel +gimme +gimps +gimpy +ginch +ginge +gings +ginks +ginny +ginzo +gipon +gippo +gippy +girds +girls +girns +giron +giros +girrs +girsh +girts +gismo +gisms +gists +gitch +gites +giust +gived +gives +gizmo +glace +glads +glady +glaik +glair +glams +glans +glary +glaum +glaur +glazy +gleba +glebe +gleby +glede +gleds +gleed +gleek +glees +gleet +gleis +glens +glent +gleys +glial +glias +glibs +gliff +glift +glike +glime +glims +glisk +glits +glitz +gloam +globi +globs +globy +glode +glogg +gloms +gloop +glops +glost +glout +glows +gloze +glued +gluer +glues +gluey +glugs +glume +glums +gluon +glute +gluts +gnarl +gnarr +gnars +gnats +gnawn +gnaws +gnows +goads +goafs +goals +goary +goats +goaty +goban +gobar +gobbi +gobbo +gobby +gobis +gobos +godet +godso +goels +goers +goest +goeth +goety +gofer +goffs +gogga +gogos +goier +gojis +golds +goldy +goles +golfs +golpe +golps +gombo +gomer +gompa +gonch +gonef +gongs +gonia +gonif +gonks +gonna +gonof +gonys +gonzo +gooby +goods +goofs +googs +gooks +gooky +goold +gools +gooly +goons +goony +goops +goopy +goors +goory +goosy +gopak +gopik +goral +goras +gored +gores +goris +gorms +gormy +gorps +gorse +gorsy +gosht +gosse +gotch +goths +gothy +gotta +gouch +gouks +goura +gouts +gouty +gowan +gowds +gowfs +gowks +gowls +gowns +goxes +goyim +goyle +graal +grabs +grads +graff +graip +grama +grame +gramp +grams +grana +grans +grapy +gravs +grays +grebe +grebo +grece +greek +grees +grege +grego +grein +grens +grese +greve +grews +greys +grice +gride +grids +griff +grift +grigs +grike +grins +griot +grips +gript +gripy +grise +grist +grisy +grith +grits +grize +groat +grody +grogs +groks +groma +grone +groof +grosz +grots +grouf +grovy +grows +grrls +grrrl +grubs +grued +grues +grufe +grume +grump +grund +gryce +gryde +gryke +grype +grypt +guaco +guana +guano +guans +guars +gucks +gucky +gudes +guffs +gugas +guids +guimp +guiro +gulag +gular +gulas +gules +gulet +gulfs +gulfy +gulls +gulph +gulps +gulpy +gumma +gummi +gumps +gundy +gunge +gungy +gunks +gunky +gunny +guqin +gurdy +gurge +gurls +gurly +gurns +gurry +gursh +gurus +gushy +gusla +gusle +gusli +gussy +gusts +gutsy +gutta +gutty +guyed +guyle +guyot +guyse +gwine +gyals +gyans +gybed +gybes +gyeld +gymps +gynae +gynie +gynny +gynos +gyoza +gypos +gyppo +gyppy +gyral +gyred +gyres +gyron +gyros +gyrus +gytes +gyved +gyves +haafs +haars +hable +habus +hacek +hacks +hadal +haded +hades +hadji +hadst +haems +haets +haffs +hafiz +hafts +haggs +hahas +haick +haika +haiks +haiku +hails +haily +hains +haint +hairs +haith +hajes +hajis +hajji +hakam +hakas +hakea +hakes +hakim +hakus +halal +haled +haler +hales +halfa +halfs +halid +hallo +halls +halma +halms +halon +halos +halse +halts +halva +halwa +hamal +hamba +hamed +hames +hammy +hamza +hanap +hance +hanch +hands +hangi +hangs +hanks +hanky +hansa +hanse +hants +haole +haoma +hapax +haply +happi +hapus +haram +hards +hared +hares +harim +harks +harls +harms +harns +haros +harps +harts +hashy +hasks +hasps +hasta +hated +hates +hatha +hauds +haufs +haugh +hauld +haulm +hauls +hault +hauns +hause +haver +haves +hawed +hawks +hawms +hawse +hayed +hayer +hayey +hayle +hazan +hazed +hazer +hazes +heads +heald +heals +heame +heaps +heapy +heare +hears +heast +heats +heben +hebes +hecht +hecks +heder +hedgy +heeds +heedy +heels +heeze +hefte +hefts +heids +heigh +heils +heirs +hejab +hejra +heled +heles +helio +hells +helms +helos +helot +helps +helve +hemal +hemes +hemic +hemin +hemps +hempy +hench +hends +henge +henna +henny +henry +hents +hepar +herbs +herby +herds +heres +herls +herma +herms +herns +heros +herry +herse +hertz +herye +hesps +hests +hetes +heths +heuch +heugh +hevea +hewed +hewer +hewgh +hexad +hexed +hexer +hexes +hexyl +heyed +hiant +hicks +hided +hider +hides +hiems +highs +hight +hijab +hijra +hiked +hiker +hikes +hikoi +hilar +hilch +hillo +hills +hilts +hilum +hilus +himbo +hinau +hinds +hings +hinky +hinny +hints +hiois +hiply +hired +hiree +hirer +hires +hissy +hists +hithe +hived +hiver +hives +hizen +hoaed +hoagy +hoars +hoary +hoast +hobos +hocks +hocus +hodad +hodja +hoers +hogan +hogen +hoggs +hoghs +hohed +hoick +hoied +hoiks +hoing +hoise +hokas +hoked +hokes +hokey +hokis +hokku +hokum +holds +holed +holes +holey +holks +holla +hollo +holme +holms +holon +holos +holts +homas +homed +homes +homey +homie +homme +homos +honan +honda +honds +honed +honer +hones +hongi +hongs +honks +honky +hooch +hoods +hoody +hooey +hoofs +hooka +hooks +hooky +hooly +hoons +hoops +hoord +hoors +hoosh +hoots +hooty +hoove +hopak +hoped +hoper +hopes +hoppy +horah +horal +horas +horis +horks +horme +horns +horst +horsy +hosed +hosel +hosen +hoser +hoses +hosey +hosta +hosts +hotch +hoten +hotty +houff +houfs +hough +houri +hours +houts +hovea +hoved +hoven +hoves +howbe +howes +howff +howfs +howks +howls +howre +howso +hoxed +hoxes +hoyas +hoyed +hoyle +hubby +hucks +hudna +hudud +huers +huffs +huffy +huger +huggy +huhus +huias +hulas +hules +hulks +hulky +hullo +hulls +hully +humas +humfs +humic +humps +humpy +hunks +hunts +hurds +hurls +hurly +hurra +hurst +hurts +hushy +husks +husos +hutia +huzza +huzzy +hwyls +hydra +hyens +hygge +hying +hykes +hylas +hyleg +hyles +hylic +hymns +hynde +hyoid +hyped +hypes +hypha +hyphy +hypos +hyrax +hyson +hythe +iambi +iambs +ibrik +icers +iched +iches +ichor +icier +icker +ickle +icons +ictal +ictic +ictus +idant +ideas +idees +ident +idled +idles +idola +idols +idyls +iftar +igapo +igged +iglus +ihram +ikans +ikats +ikons +ileac +ileal +ileum +ileus +iliad +ilial +ilium +iller +illth +imago +imams +imari +imaum +imbar +imbed +imide +imido +imids +imine +imino +immew +immit +immix +imped +impis +impot +impro +imshi +imshy +inapt +inarm +inbye +incel +incle +incog +incus +incut +indew +india +indie +indol +indow +indri +indue +inerm +infix +infos +infra +ingan +ingle +inion +inked +inker +inkle +inned +innit +inorb +inrun +inset +inspo +intel +intil +intis +intra +inula +inure +inurn +inust +invar +inwit +iodic +iodid +iodin +iotas +ippon +irade +irids +iring +irked +iroko +irone +irons +isbas +ishes +isled +isles +isnae +issei +istle +items +ither +ivied +ivies +ixias +ixnay +ixora +ixtle +izard +izars +izzat +jaaps +jabot +jacal +jacks +jacky +jaded +jades +jafas +jaffa +jagas +jager +jaggs +jaggy +jagir +jagra +jails +jaker +jakes +jakey +jalap +jalop +jambe +jambo +jambs +jambu +james +jammy +jamon +janes +janns +janny +janty +japan +japed +japer +japes +jarks +jarls +jarps +jarta +jarul +jasey +jaspe +jasps +jatos +jauks +jaups +javas +javel +jawan +jawed +jaxie +jeans +jeats +jebel +jedis +jeels +jeely +jeeps +jeers +jeeze +jefes +jeffs +jehad +jehus +jelab +jello +jells +jembe +jemmy +jenny +jeons +jerid +jerks +jerry +jesse +jests +jesus +jetes +jeton +jeune +jewed +jewie +jhala +jiaos +jibba +jibbs +jibed +jiber +jibes +jiffs +jiggy +jigot +jihad +jills +jilts +jimmy +jimpy +jingo +jinks +jinne +jinni +jinns +jirds +jirga +jirre +jisms +jived +jiver +jives +jivey +jnana +jobed +jobes +jocko +jocks +jocky +jocos +jodel +joeys +johns +joins +joked +jokes +jokey +jokol +joled +joles +jolls +jolts +jolty +jomon +jomos +jones +jongs +jonty +jooks +joram +jorum +jotas +jotty +jotun +joual +jougs +jouks +joule +jours +jowar +jowed +jowls +jowly +joyed +jubas +jubes +jucos +judas +judgy +judos +jugal +jugum +jujus +juked +jukes +jukus +julep +jumar +jumby +jumps +junco +junks +junky +jupes +jupon +jural +jurat +jurel +jures +justs +jutes +jutty +juves +juvie +kaama +kabab +kabar +kabob +kacha +kacks +kadai +kades +kadis +kafir +kagos +kagus +kahal +kaiak +kaids +kaies +kaifs +kaika +kaiks +kails +kaims +kaing +kains +kakas +kakis +kalam +kales +kalif +kalis +kalpa +kamas +kames +kamik +kamis +kamme +kanae +kanas +kandy +kaneh +kanes +kanga +kangs +kanji +kants +kanzu +kaons +kapas +kaphs +kapok +kapow +kapus +kaput +karas +karat +karks +karns +karoo +karos +karri +karst +karsy +karts +karzy +kasha +kasme +katal +katas +katis +katti +kaugh +kauri +kauru +kaury +kaval +kavas +kawas +kawau +kawed +kayle +kayos +kazis +kazoo +kbars +kebar +kebob +kecks +kedge +kedgy +keech +keefs +keeks +keels +keema +keeno +keens +keeps +keets +keeve +kefir +kehua +keirs +kelep +kelim +kells +kelly +kelps +kelpy +kelts +kelty +kembo +kembs +kemps +kempt +kempy +kenaf +kench +kendo +kenos +kente +kents +kepis +kerbs +kerel +kerfs +kerky +kerma +kerne +kerns +keros +kerry +kerve +kesar +kests +ketas +ketch +ketes +ketol +kevel +kevil +kexes +keyed +keyer +khadi +khafs +khans +khaph +khats +khaya +khazi +kheda +kheth +khets +khoja +khors +khoum +khuds +kiaat +kiack +kiang +kibbe +kibbi +kibei +kibes +kibla +kicks +kicky +kiddo +kiddy +kidel +kidge +kiefs +kiers +kieve +kievs +kight +kikes +kikoi +kiley +kilim +kills +kilns +kilos +kilps +kilts +kilty +kimbo +kinas +kinda +kinds +kindy +kines +kings +kinin +kinks +kinos +kiore +kipes +kippa +kipps +kirby +kirks +kirns +kirri +kisan +kissy +kists +kited +kiter +kites +kithe +kiths +kitul +kivas +kiwis +klang +klaps +klett +klick +klieg +kliks +klong +kloof +kluge +klutz +knags +knaps +knarl +knars +knaur +knawe +knees +knell +knish +knits +knive +knobs +knops +knosp +knots +knout +knowe +knows +knubs +knurl +knurr +knurs +knuts +koans +koaps +koban +kobos +koels +koffs +kofta +kogal +kohas +kohen +kohls +koine +kojis +kokam +kokas +koker +kokra +kokum +kolas +kolos +kombu +konbu +kondo +konks +kooks +kooky +koori +kopek +kophs +kopje +koppa +korai +koras +korat +kores +korma +koros +korun +korus +koses +kotch +kotos +kotow +koura +kraal +krabs +kraft +krais +krait +krang +krans +kranz +kraut +krays +kreep +kreng +krewe +krona +krone +kroon +krubi +krunk +ksars +kubie +kudos +kudus +kudzu +kufis +kugel +kuias +kukri +kukus +kulak +kulan +kulas +kulfi +kumis +kumys +kuris +kurre +kurta +kurus +kusso +kutas +kutch +kutis +kutus +kuzus +kvass +kvell +kwela +kyack +kyaks +kyang +kyars +kyats +kybos +kydst +kyles +kylie +kylin +kylix +kyloe +kynde +kynds +kypes +kyrie +kytes +kythe +laari +labda +labia +labis +labra +laced +lacer +laces +lacet +lacey +lacks +laddy +laded +lader +lades +laers +laevo +lagan +lahal +lahar +laich +laics +laids +laigh +laika +laiks +laird +lairs +lairy +laith +laity +laked +laker +lakes +lakhs +lakin +laksa +laldy +lalls +lamas +lambs +lamby +lamed +lamer +lames +lamia +lammy +lamps +lanai +lanas +lanch +lande +lands +lanes +lanks +lants +lapin +lapis +lapje +larch +lards +lardy +laree +lares +largo +laris +larks +larky +larns +larnt +larum +lased +laser +lases +lassi +lassu +lassy +lasts +latah +lated +laten +latex +lathi +laths +lathy +latke +latus +lauan +lauch +lauds +laufs +laund +laura +laval +lavas +laved +laver +laves +lavra +lavvy +lawed +lawer +lawin +lawks +lawns +lawny +laxed +laxer +laxes +laxly +layed +layin +layup +lazar +lazed +lazes +lazos +lazzi +lazzo +leads +leady +leafs +leaks +leams +leans +leany +leaps +leare +lears +leary +leats +leavy +leaze +leben +leccy +ledes +ledgy +ledum +leear +leeks +leeps +leers +leese +leets +leeze +lefte +lefts +leger +leges +legge +leggo +legit +lehrs +lehua +leirs +leish +leman +lemed +lemel +lemes +lemma +lemme +lends +lenes +lengs +lenis +lenos +lense +lenti +lento +leone +lepid +lepra +lepta +lered +leres +lerps +lesbo +leses +lests +letch +lethe +letup +leuch +leuco +leuds +leugh +levas +levee +leves +levin +levis +lewis +lexes +lexis +lezes +lezza +lezzy +liana +liane +liang +liard +liars +liart +liber +libra +libri +lichi +licht +licit +licks +lidar +lidos +liefs +liens +liers +lieus +lieve +lifer +lifes +lifts +ligan +liger +ligge +ligne +liked +liker +likes +likin +lills +lilos +lilts +liman +limas +limax +limba +limbi +limbs +limby +limed +limen +limes +limey +limma +limns +limos +limpa +limps +linac +linch +linds +lindy +lined +lines +liney +linga +lings +lingy +linin +links +linky +linns +linny +linos +lints +linty +linum +linux +lions +lipas +lipes +lipin +lipos +lippy +liras +lirks +lirot +lisks +lisle +lisps +lists +litai +litas +lited +liter +lites +litho +liths +litre +lived +liven +lives +livor +livre +llano +loach +loads +loafs +loams +loans +loast +loave +lobar +lobed +lobes +lobos +lobus +loche +lochs +locie +locis +locks +locos +locum +loden +lodes +loess +lofts +logan +loges +loggy +logia +logie +logoi +logon +logos +lohan +loids +loins +loipe +loirs +lokes +lolls +lolly +lolog +lomas +lomed +lomes +loner +longa +longe +longs +looby +looed +looey +loofa +loofs +looie +looks +looky +looms +loons +loony +loops +loord +loots +loped +loper +lopes +loppy +loral +loran +lords +lordy +lorel +lores +loric +loris +losed +losel +losen +loses +lossy +lotah +lotas +lotes +lotic +lotos +lotsa +lotta +lotte +lotto +lotus +loued +lough +louie +louis +louma +lound +louns +loupe +loups +loure +lours +loury +louts +lovat +loved +loves +lovey +lovie +lowan +lowed +lowes +lownd +lowne +lowns +lowps +lowry +lowse +lowts +loxed +loxes +lozen +luach +luaus +lubed +lubes +lubra +luces +lucks +lucre +ludes +ludic +ludos +luffa +luffs +luged +luger +luges +lulls +lulus +lumas +lumbi +lumme +lummy +lumps +lunas +lunes +lunet +lungi +lungs +lunks +lunts +lupin +lured +lurer +lures +lurex +lurgi +lurgy +lurks +lurry +lurve +luser +lushy +lusks +lusts +lusus +lutea +luted +luter +lutes +luvvy +luxed +luxer +luxes +lweis +lyams +lyard +lyart +lyase +lycea +lycee +lycra +lymes +lynes +lyres +lysed +lyses +lysin +lysis +lysol +lyssa +lyted +lytes +lythe +lytic +lytta +maaed +maare +maars +mabes +macas +maced +macer +maces +mache +machi +machs +macks +macle +macon +madge +madid +madre +maerl +mafic +mages +maggs +magot +magus +mahoe +mahua +mahwa +maids +maiko +maiks +maile +maill +mails +maims +mains +maire +mairs +maise +maist +makar +makes +makis +makos +malam +malar +malas +malax +males +malic +malik +malis +malls +malms +malmy +malts +malty +malus +malva +malwa +mamas +mamba +mamee +mamey +mamie +manas +manat +mandi +maneb +maned +maneh +manes +manet +mangs +manis +manky +manna +manos +manse +manta +manto +manty +manul +manus +mapau +maqui +marae +marah +maras +marcs +mardy +mares +marge +margs +maria +marid +marka +marks +marle +marls +marly +marms +maron +maror +marra +marri +marse +marts +marvy +masas +mased +maser +mases +mashy +masks +massa +massy +masts +masty +masus +matai +mated +mater +mates +maths +matin +matlo +matte +matts +matza +matzo +mauby +mauds +mauls +maund +mauri +mausy +mauts +mauzy +maven +mavie +mavin +mavis +mawed +mawks +mawky +mawns +mawrs +maxed +maxes +maxis +mayan +mayas +mayed +mayos +mayst +mazed +mazer +mazes +mazey +mazut +mbira +meads +meals +meane +means +meany +meare +mease +meath +meats +mebos +mechs +mecks +medii +medle +meeds +meers +meets +meffs +meins +meint +meiny +meith +mekka +melas +melba +melds +melic +melik +mells +melts +melty +memes +memos +menad +mends +mened +menes +menge +mengs +mensa +mense +mensh +menta +mento +menus +meous +meows +merch +mercs +merde +mered +merel +merer +meres +meril +meris +merks +merle +merls +merse +mesal +mesas +mesel +meses +meshy +mesic +mesne +meson +messy +mesto +meted +metes +metho +meths +metic +metif +metis +metol +metre +meuse +meved +meves +mewed +mewls +meynt +mezes +mezze +mezzo +mhorr +miaou +miaow +miasm +miaul +micas +miche +micht +micks +micky +micos +micra +middy +midgy +midis +miens +mieve +miffs +miffy +mifty +miggs +mihas +mihis +miked +mikes +mikra +mikva +milch +milds +miler +miles +milfs +milia +milko +milks +mille +mills +milor +milos +milpa +milts +milty +miltz +mimed +mimeo +mimer +mimes +mimsy +minae +minar +minas +mincy +minds +mined +mines +minge +mings +mingy +minis +minke +minks +minny +minos +mints +mired +mires +mirex +mirid +mirin +mirks +mirky +mirly +miros +mirvs +mirza +misch +misdo +mises +misgo +misos +missa +mists +misty +mitch +miter +mites +mitis +mitre +mitts +mixed +mixen +mixer +mixes +mixte +mixup +mizen +mizzy +mneme +moans +moats +mobby +mobes +mobey +mobie +moble +mochi +mochs +mochy +mocks +moder +modes +modge +modii +modus +moers +mofos +moggy +mohel +mohos +mohrs +mohua +mohur +moile +moils +moira +moire +moits +mojos +mokes +mokis +mokos +molal +molas +molds +moled +moles +molla +molls +molly +molto +molts +molys +momes +momma +mommy +momus +monad +monal +monas +monde +mondo +moner +mongo +mongs +monic +monie +monks +monos +monte +monty +moobs +mooch +moods +mooed +mooks +moola +mooli +mools +mooly +moong +moons +moony +moops +moors +moory +moots +moove +moped +moper +mopes +mopey +moppy +mopsy +mopus +morae +moras +morat +moray +morel +mores +moria +morne +morns +morra +morro +morse +morts +mosed +moses +mosey +mosks +mosso +moste +mosts +moted +moten +motes +motet +motey +moths +mothy +motis +motte +motts +motty +motus +motza +mouch +moues +mould +mouls +moups +moust +mousy +moved +moves +mowas +mowed +mowra +moxas +moxie +moyas +moyle +moyls +mozed +mozes +mozos +mpret +mucho +mucic +mucid +mucin +mucks +mucor +mucro +mudge +mudir +mudra +muffs +mufti +mugga +muggs +muggy +muhly +muids +muils +muirs +muist +mujik +mulct +muled +mules +muley +mulga +mulie +mulla +mulls +mulse +mulsh +mumms +mumps +mumsy +mumus +munga +munge +mungo +mungs +munis +munts +muntu +muons +muras +mured +mures +murex +murid +murks +murls +murly +murra +murre +murri +murrs +murry +murti +murva +musar +musca +mused +muser +muses +muset +musha +musit +musks +musos +musse +mussy +musth +musts +mutch +muted +muter +mutes +mutha +mutis +muton +mutts +muxed +muxes +muzak +muzzy +mvule +myall +mylar +mynah +mynas +myoid +myoma +myope +myops +myopy +mysid +mythi +myths +mythy +myxos +mzees +naams +naans +nabes +nabis +nabks +nabla +nabob +nache +nacho +nacre +nadas +naeve +naevi +naffs +nagas +naggy +nagor +nahal +naiad +naifs +naiks +nails +naira +nairu +naked +naker +nakfa +nalas +naled +nalla +named +namer +names +namma +namus +nanas +nance +nancy +nandu +nanna +nanos +nanua +napas +naped +napes +napoo +nappa +nappe +nappy +naras +narco +narcs +nards +nares +naric +naris +narks +narky +narre +nashi +natch +nates +natis +natty +nauch +naunt +navar +naves +navew +navvy +nawab +nazes +nazir +nazis +nduja +neafe +neals +neaps +nears +neath +neats +nebek +nebel +necks +neddy +needs +neeld +neele +neemb +neems +neeps +neese +neeze +negro +negus +neifs +neist +neive +nelis +nelly +nemas +nemns +nempt +nenes +neons +neper +nepit +neral +nerds +nerka +nerks +nerol +nerts +nertz +nervy +nests +netes +netop +netts +netty +neuks +neume +neums +nevel +neves +nevus +newbs +newed +newel +newie +newsy +newts +nexts +nexus +ngaio +ngana +ngati +ngoma +ngwee +nicad +nicht +nicks +nicol +nidal +nided +nides +nidor +nidus +niefs +nieve +nifes +niffs +niffy +nifty +niger +nighs +nihil +nikab +nikah +nikau +nills +nimbi +nimbs +nimps +niner +nines +ninon +nipas +nippy +niqab +nirls +nirly +nisei +nisse +nisus +niter +nites +nitid +niton +nitre +nitro +nitry +nitty +nival +nixed +nixer +nixes +nixie +nizam +nkosi +noahs +nobby +nocks +nodal +noddy +nodes +nodus +noels +noggs +nohow +noils +noily +noint +noirs +noles +nolls +nolos +nomas +nomen +nomes +nomic +nomoi +nomos +nonas +nonce +nones +nonet +nongs +nonis +nonny +nonyl +noobs +nooit +nooks +nooky +noons +noops +nopal +noria +noris +norks +norma +norms +nosed +noser +noses +notal +noted +noter +notes +notum +nould +noule +nouls +nouns +nouny +noups +novae +novas +novum +noway +nowed +nowls +nowts +nowty +noxal +noxes +noyau +noyed +noyes +nubby +nubia +nucha +nuddy +nuder +nudes +nudie +nudzh +nuffs +nugae +nuked +nukes +nulla +nulls +numbs +numen +nummy +nunny +nurds +nurdy +nurls +nurrs +nutso +nutsy +nyaff +nyala +nying +nyssa +oaked +oaker +oakum +oared +oases +oasis +oasts +oaten +oater +oaths +oaves +obang +obeah +obeli +obeys +obias +obied +obiit +obits +objet +oboes +obole +oboli +obols +occam +ocher +oches +ochre +ochry +ocker +ocrea +octad +octan +octas +octyl +oculi +odahs +odals +odeon +odeum +odism +odist +odium +odors +odour +odyle +odyls +ofays +offed +offie +oflag +ofter +ogams +ogeed +ogees +oggin +ogham +ogive +ogled +ogler +ogles +ogmic +ogres +ohias +ohing +ohmic +ohone +oidia +oiled +oiler +oinks +oints +ojime +okapi +okays +okehs +okras +oktas +oldie +oleic +olein +olent +oleos +oleum +olios +ollas +ollav +oller +ollie +ology +olpae +olpes +omasa +omber +ombus +omens +omers +omits +omlah +omovs +omrah +oncer +onces +oncet +oncus +onely +oners +onery +onium +onkus +onlay +onned +ontic +oobit +oohed +oomph +oonts +ooped +oorie +ooses +ootid +oozed +oozes +opahs +opals +opens +opepe +oping +oppos +opsin +opted +opter +orach +oracy +orals +orang +orant +orate +orbed +orcas +orcin +ordos +oread +orfes +orgia +orgic +orgue +oribi +oriel +orixa +orles +orlon +orlop +ormer +ornis +orpin +orris +ortho +orval +orzos +oscar +oshac +osier +osmic +osmol +ossia +ostia +otaku +otary +ottar +ottos +oubit +oucht +ouens +ouija +oulks +oumas +oundy +oupas +ouped +ouphe +ouphs +ourie +ousel +ousts +outby +outed +outre +outro +outta +ouzel +ouzos +ovals +ovels +ovens +overs +ovist +ovoli +ovolo +ovule +owche +owies +owled +owler +owlet +owned +owres +owrie +owsen +oxbow +oxers +oxeye +oxids +oxies +oxime +oxims +oxlip +oxter +oyers +ozeki +ozzie +paals +paans +pacas +paced +pacer +paces +pacey +pacha +packs +pacos +pacta +pacts +padis +padle +padma +padre +padri +paean +paedo +paeon +paged +pager +pages +pagle +pagod +pagri +paiks +pails +pains +paire +pairs +paisa +paise +pakka +palas +palay +palea +paled +pales +palet +palis +palki +palla +palls +pally +palms +palmy +palpi +palps +palsa +pampa +panax +pance +panda +pands +pandy +paned +panes +panga +pangs +panim +panko +panne +panni +panto +pants +panty +paoli +paolo +papas +papaw +papes +pappi +pappy +parae +paras +parch +pardi +pards +pardy +pared +paren +pareo +pares +pareu +parev +parge +pargo +paris +parki +parks +parky +parle +parly +parma +parol +parps +parra +parrs +parti +parts +parve +parvo +paseo +pases +pasha +pashm +paska +paspy +passe +pasts +pated +paten +pater +pates +paths +patin +patka +patly +patte +patus +pauas +pauls +pavan +paved +paven +paver +paves +pavid +pavin +pavis +pawas +pawaw +pawed +pawer +pawks +pawky +pawls +pawns +paxes +payed +payor +paysd +peage +peags +peaks +peaky +peals +peans +peare +pears +peart +pease +peats +peaty +peavy +peaze +pebas +pechs +pecke +pecks +pecky +pedes +pedis +pedro +peece +peeks +peels +peens +peeoy +peepe +peeps +peers +peery +peeve +peggy +peghs +peins +peise +peize +pekan +pekes +pekin +pekoe +pelas +pelau +peles +pelfs +pells +pelma +pelon +pelta +pelts +pends +pendu +pened +penes +pengo +penie +penis +penks +penna +penni +pents +peons +peony +pepla +pepos +peppy +pepsi +perai +perce +percs +perdu +perdy +perea +peres +peris +perks +perms +perns +perog +perps +perry +perse +perst +perts +perve +pervo +pervs +pervy +pesos +pests +pesty +petar +peter +petit +petre +petri +petti +petto +pewee +pewit +peyse +phage +phang +phare +pharm +pheer +phene +pheon +phese +phial +phish +phizz +phlox +phoca +phono +phons +phots +phpht +phuts +phyla +phyle +piani +pians +pibal +pical +picas +piccy +picks +picot +picra +picul +piend +piers +piert +pieta +piets +piezo +pight +pigmy +piing +pikas +pikau +piked +piker +pikes +pikey +pikis +pikul +pilae +pilaf +pilao +pilar +pilau +pilaw +pilch +pilea +piled +pilei +piler +piles +pilis +pills +pilow +pilum +pilus +pimas +pimps +pinas +pined +pines +pingo +pings +pinko +pinks +pinna +pinny +pinon +pinot +pinta +pints +pinup +pions +piony +pious +pioye +pioys +pipal +pipas +piped +pipes +pipet +pipis +pipit +pippy +pipul +pirai +pirls +pirns +pirog +pisco +pises +pisky +pisos +pissy +piste +pitas +piths +piton +pitot +pitta +piums +pixes +pized +pizes +plaas +plack +plage +plans +plaps +plash +plasm +plast +plats +platt +platy +playa +plays +pleas +plebe +plebs +plena +pleon +plesh +plews +plica +plies +plims +pling +plink +ploat +plods +plong +plonk +plook +plops +plots +plotz +plouk +plows +ploye +ploys +plues +pluff +plugs +plums +plumy +pluot +pluto +plyer +poach +poaka +poake +poboy +pocks +pocky +podal +poddy +podex +podge +podgy +podia +poems +poeps +poets +pogey +pogge +pogos +pohed +poilu +poind +pokal +poked +pokes +pokey +pokie +poled +poler +poles +poley +polio +polis +polje +polks +polls +polly +polos +polts +polys +pombe +pomes +pommy +pomos +pomps +ponce +poncy +ponds +pones +poney +ponga +pongo +pongs +pongy +ponks +ponts +ponty +ponzu +poods +pooed +poofs +poofy +poohs +pooja +pooka +pooks +pools +poons +poops +poopy +poori +poort +poots +poove +poovy +popes +poppa +popsy +porae +poral +pored +porer +pores +porge +porgy +porin +porks +porky +porno +porns +porny +porta +ports +porty +posed +poses +posey +posho +posts +potae +potch +poted +potes +potin +potoo +potsy +potto +potts +potty +pouff +poufs +pouke +pouks +poule +poulp +poult +poupe +poupt +pours +pouts +powan +powin +pownd +powns +powny +powre +poxed +poxes +poynt +poyou +poyse +pozzy +praam +prads +prahu +prams +prana +prang +praos +prase +prate +prats +pratt +praty +praus +prays +predy +preed +prees +preif +prems +premy +prent +preon +preop +preps +presa +prese +prest +preve +prexy +preys +prial +pricy +prief +prier +pries +prigs +prill +prima +primi +primp +prims +primy +prink +prion +prise +priss +proas +probs +prods +proem +profs +progs +proin +proke +prole +proll +promo +proms +pronk +props +prore +proso +pross +prost +prosy +proto +proul +prows +proyn +prunt +pruta +pryer +pryse +pseud +pshaw +psion +psoae +psoai +psoas +psora +psych +psyop +pubco +pubes +pubis +pucan +pucer +puces +pucka +pucks +puddy +pudge +pudic +pudor +pudsy +pudus +puers +puffa +puffs +puggy +pugil +puhas +pujah +pujas +pukas +puked +puker +pukes +pukey +pukka +pukus +pulao +pulas +puled +puler +pules +pulik +pulis +pulka +pulks +pulli +pulls +pully +pulmo +pulps +pulus +pumas +pumie +pumps +punas +punce +punga +pungs +punji +punka +punks +punky +punny +punto +punts +punty +pupae +pupas +pupus +purda +pured +pures +purin +puris +purls +purpy +purrs +pursy +purty +puses +pusle +pussy +putid +puton +putti +putto +putts +puzel +pwned +pyats +pyets +pygal +pyins +pylon +pyned +pynes +pyoid +pyots +pyral +pyran +pyres +pyrex +pyric +pyros +pyxed +pyxes +pyxie +pyxis +pzazz +qadis +qaids +qajaq +qanat +qapik +qibla +qophs +qorma +quads +quaff +quags +quair +quais +quaky +quale +quant +quare +quass +quate +quats +quayd +quays +qubit +quean +queme +quena +quern +queyn +queys +quich +quids +quiff +quims +quina +quine +quino +quins +quint +quipo +quips +quipu +quire +quirt +quist +quits +quoad +quods +quoif +quoin +quoit +quoll +quonk +quops +qursh +quyte +rabat +rabic +rabis +raced +races +rache +racks +racon +radge +radix +radon +raffs +rafts +ragas +ragde +raged +ragee +rager +rages +ragga +raggs +raggy +ragis +ragus +rahed +rahui +raias +raids +raiks +raile +rails +raine +rains +raird +raita +raits +rajas +rajes +raked +rakee +raker +rakes +rakia +rakis +rakus +rales +ramal +ramee +ramet +ramie +ramin +ramis +rammy +ramps +ramus +ranas +rance +rands +ranee +ranga +rangi +rangs +rangy +ranid +ranis +ranke +ranks +rants +raped +raper +rapes +raphe +rappe +rared +raree +rares +rarks +rased +raser +rases +rasps +rasse +rasta +ratal +ratan +ratas +ratch +rated +ratel +rater +rates +ratha +rathe +raths +ratoo +ratos +ratus +rauns +raupo +raved +ravel +raver +raves +ravey +ravin +rawer +rawin +rawly +rawns +raxed +raxes +rayah +rayas +rayed +rayle +rayne +razed +razee +razer +razes +razoo +readd +reads +reais +reaks +realo +reals +reame +reams +reamy +reans +reaps +rears +reast +reata +reate +reave +rebbe +rebec +rebid +rebit +rebop +rebuy +recal +recce +recco +reccy +recit +recks +recon +recta +recti +recto +redan +redds +reddy +reded +redes +redia +redid +redip +redly +redon +redos +redox +redry +redub +redux +redye +reech +reede +reeds +reefs +reefy +reeks +reeky +reels +reens +reest +reeve +refed +refel +reffo +refis +refix +refly +refry +regar +reges +reggo +regie +regma +regna +regos +regur +rehem +reifs +reify +reiki +reiks +reink +reins +reird +reist +reive +rejig +rejon +reked +rekes +rekey +relet +relie +relit +rello +reman +remap +remen +remet +remex +remix +renay +rends +reney +renga +renig +renin +renne +renos +rente +rents +reoil +reorg +repeg +repin +repla +repos +repot +repps +repro +reran +rerig +resat +resaw +resay +resee +reses +resew +resid +resit +resod +resow +resto +rests +resty +resus +retag +retax +retem +retia +retie +retox +revet +revie +rewan +rewax +rewed +rewet +rewin +rewon +rewth +rexes +rezes +rheas +rheme +rheum +rhies +rhime +rhine +rhody +rhomb +rhone +rhumb +rhyne +rhyta +riads +rials +riant +riata +ribas +ribby +ribes +riced +ricer +rices +ricey +richt +ricin +ricks +rides +ridgy +ridic +riels +riems +rieve +rifer +riffs +rifte +rifts +rifty +riggs +rigol +riled +riles +riley +rille +rills +rimae +rimed +rimer +rimes +rimus +rinds +rindy +rines +rings +rinks +rioja +riots +riped +ripes +ripps +rises +rishi +risks +risps +risus +rites +ritts +ritzy +rivas +rived +rivel +riven +rives +riyal +rizas +roads +roams +roans +roars +roary +roate +robed +robes +roble +rocks +roded +rodes +roguy +rohes +roids +roils +roily +roins +roist +rojak +rojis +roked +roker +rokes +rolag +roles +rolfs +rolls +romal +roman +romeo +romps +ronde +rondo +roneo +rones +ronin +ronne +ronte +ronts +roods +roofs +roofy +rooks +rooky +rooms +roons +roops +roopy +roosa +roose +roots +rooty +roped +roper +ropes +ropey +roque +roral +rores +roric +rorid +rorie +rorts +rorty +rosed +roses +roset +roshi +rosin +rosit +rosti +rosts +rotal +rotan +rotas +rotch +roted +rotes +rotis +rotls +roton +rotos +rotte +rouen +roues +roule +rouls +roums +roups +roupy +roust +routh +routs +roved +roven +roves +rowan +rowed +rowel +rowen +rowie +rowme +rownd +rowth +rowts +royne +royst +rozet +rozit +ruana +rubai +rubby +rubel +rubes +rubin +ruble +rubli +rubus +ruche +rucks +rudas +rudds +rudes +rudie +rudis +rueda +ruers +ruffe +ruffs +rugae +rugal +ruggy +ruing +ruins +rukhs +ruled +rules +rumal +rumbo +rumen +rumes +rumly +rummy +rumpo +rumps +rumpy +runch +runds +runed +runes +rungs +runic +runny +runts +runty +rupia +rurps +rurus +rusas +ruses +rushy +rusks +rusma +russe +rusts +ruths +rutin +rutty +ryals +rybat +ryked +rykes +rymme +rynds +ryots +ryper +saags +sabal +sabed +saber +sabes +sabha +sabin +sabir +sable +sabot +sabra +sabre +sacks +sacra +saddo +sades +sadhe +sadhu +sadis +sados +sadza +safed +safes +sagas +sager +sages +saggy +sagos +sagum +saheb +sahib +saice +saick +saics +saids +saiga +sails +saims +saine +sains +sairs +saist +saith +sajou +sakai +saker +sakes +sakia +sakis +sakti +salal +salat +salep +sales +salet +salic +salix +salle +salmi +salol +salop +salpa +salps +salse +salto +salts +salue +salut +saman +samas +samba +sambo +samek +samel +samen +sames +samey +samfu +sammy +sampi +samps +sands +saned +sanes +sanga +sangh +sango +sangs +sanko +sansa +santo +sants +saola +sapan +sapid +sapor +saran +sards +sared +saree +sarge +sargo +sarin +saris +sarks +sarky +sarod +saros +sarus +saser +sasin +sasse +satai +satay +sated +satem +sates +satis +sauba +sauch +saugh +sauls +sault +saunt +saury +sauts +saved +saver +saves +savey +savin +sawah +sawed +sawer +saxes +sayed +sayer +sayid +sayne +sayon +sayst +sazes +scabs +scads +scaff +scags +scail +scala +scall +scams +scand +scans +scapa +scape +scapi +scarp +scars +scart +scath +scats +scatt +scaud +scaup +scaur +scaws +sceat +scena +scend +schav +schmo +schul +schwa +sclim +scody +scogs +scoog +scoot +scopa +scops +scots +scoug +scoup +scowp +scows +scrab +scrae +scrag +scran +scrat +scraw +scray +scrim +scrip +scrob +scrod +scrog +scrow +scudi +scudo +scuds +scuff +scuft +scugs +sculk +scull +sculp +sculs +scums +scups +scurf +scurs +scuse +scuta +scute +scuts +scuzz +scyes +sdayn +sdein +seals +seame +seams +seamy +seans +seare +sears +sease +seats +seaze +sebum +secco +sechs +sects +seder +sedes +sedge +sedgy +sedum +seeds +seeks +seeld +seels +seely +seems +seeps +seepy +seers +sefer +segar +segni +segno +segol +segos +sehri +seifs +seils +seine +seirs +seise +seism +seity +seiza +sekos +sekts +selah +seles +selfs +sella +selle +sells +selva +semee +semes +semie +semis +senas +sends +senes +sengi +senna +senor +sensa +sensi +sente +senti +sents +senvy +senza +sepad +sepal +sepic +sepoy +septa +septs +serac +serai +seral +sered +serer +seres +serfs +serge +seric +serin +serks +seron +serow +serra +serre +serrs +serry +servo +sesey +sessa +setae +setal +seton +setts +sewan +sewar +sewed +sewel +sewen +sewin +sexed +sexer +sexes +sexto +sexts +seyen +shads +shags +shahs +shako +shakt +shalm +shaly +shama +shams +shand +shans +shaps +sharn +shash +shaul +shawm +shawn +shaws +shaya +shays +shchi +sheaf +sheal +sheas +sheds +sheel +shend +shent +sheol +sherd +shere +shero +shets +sheva +shewn +shews +shiai +shiel +shier +shies +shill +shily +shims +shins +ships +shirr +shirs +shish +shiso +shist +shite +shits +shiur +shiva +shive +shivs +shlep +shlub +shmek +shmoe +shoat +shoed +shoer +shoes +shogi +shogs +shoji +shojo +shola +shool +shoon +shoos +shope +shops +shorl +shote +shots +shott +showd +shows +shoyu +shred +shris +shrow +shtik +shtum +shtup +shule +shuln +shuls +shuns +shura +shute +shuts +shwas +shyer +sials +sibbs +sibyl +sices +sicht +sicko +sicks +sicky +sidas +sided +sider +sides +sidha +sidhe +sidle +sield +siens +sient +sieth +sieur +sifts +sighs +sigil +sigla +signa +signs +sijos +sikas +siker +sikes +silds +siled +silen +siler +siles +silex +silks +sills +silos +silts +silty +silva +simar +simas +simba +simis +simps +simul +sinds +sined +sines +sings +sinhs +sinks +sinky +sinus +siped +sipes +sippy +sired +siree +sires +sirih +siris +siroc +sirra +sirup +sisal +sises +sista +sists +sitar +sited +sites +sithe +sitka +situp +situs +siver +sixer +sixes +sixmo +sixte +sizar +sized +sizel +sizer +sizes +skags +skail +skald +skank +skart +skats +skatt +skaws +skean +skear +skeds +skeed +skeef +skeen +skeer +skees +skeet +skegg +skegs +skein +skelf +skell +skelm +skelp +skene +skens +skeos +skeps +skers +skets +skews +skids +skied +skies +skiey +skimo +skims +skink +skins +skint +skios +skips +skirl +skirr +skite +skits +skive +skivy +sklim +skoal +skody +skoff +skogs +skols +skool +skort +skosh +skran +skrik +skuas +skugs +skyed +skyer +skyey +skyfs +skyre +skyrs +skyte +slabs +slade +slaes +slags +slaid +slake +slams +slane +slank +slaps +slart +slats +slaty +slaws +slays +slebs +sleds +sleer +slews +sleys +slier +slily +slims +slipe +slips +slipt +slish +slits +slive +sloan +slobs +sloes +slogs +sloid +slojd +slomo +sloom +sloot +slops +slopy +slorm +slots +slove +slows +sloyd +slubb +slubs +slued +slues +sluff +slugs +sluit +slums +slurb +slurs +sluse +sluts +slyer +slype +smaak +smaik +smalm +smalt +smarm +smaze +smeek +smees +smeik +smeke +smerk +smews +smirr +smirs +smits +smogs +smoko +smolt +smoor +smoot +smore +smorg +smout +smowt +smugs +smurs +smush +smuts +snabs +snafu +snags +snaps +snarf +snark +snars +snary +snash +snath +snaws +snead +sneap +snebs +sneck +sneds +sneed +snees +snell +snibs +snick +snies +snift +snigs +snips +snipy +snirt +snits +snobs +snods +snoek +snoep +snogs +snoke +snood +snook +snool +snoot +snots +snowk +snows +snubs +snugs +snush +snyes +soaks +soaps +soare +soars +soave +sobas +socas +soces +socko +socks +socle +sodas +soddy +sodic +sodom +sofar +sofas +softa +softs +softy +soger +sohur +soils +soily +sojas +sojus +sokah +soken +sokes +sokol +solah +solan +solas +solde +soldi +soldo +solds +soled +solei +soler +soles +solon +solos +solum +solus +soman +somas +sonce +sonde +sones +songs +sonly +sonne +sonny +sonse +sonsy +sooey +sooks +sooky +soole +sools +sooms +soops +soote +soots +sophs +sophy +sopor +soppy +sopra +soral +soras +sorbo +sorbs +sorda +sordo +sords +sored +soree +sorel +sorer +sores +sorex +sorgo +sorns +sorra +sorta +sorts +sorus +soths +sotol +souce +souct +sough +souks +souls +soums +soups +soupy +sours +souse +souts +sowar +sowce +sowed +sowff +sowfs +sowle +sowls +sowms +sownd +sowne +sowps +sowse +sowth +soyas +soyle +soyuz +sozin +spacy +spado +spaed +spaer +spaes +spags +spahi +spail +spain +spait +spake +spald +spale +spall +spalt +spams +spane +spang +spans +spard +spars +spart +spate +spats +spaul +spawl +spaws +spayd +spays +spaza +spazz +speal +spean +speat +specs +spect +speel +speer +speil +speir +speks +speld +spelk +speos +spets +speug +spews +spewy +spial +spica +spick +spics +spide +spier +spies +spiff +spifs +spiks +spile +spims +spina +spink +spins +spirt +spiry +spits +spitz +spivs +splay +splog +spode +spods +spoom +spoor +spoot +spork +sposh +spots +sprad +sprag +sprat +spred +sprew +sprit +sprod +sprog +sprue +sprug +spuds +spued +spuer +spues +spugs +spule +spume +spumy +spurs +sputa +spyal +spyre +squab +squaw +squeg +squid +squit +squiz +stabs +stade +stags +stagy +staig +stane +stang +staph +staps +starn +starr +stars +stats +staun +staws +stays +stean +stear +stedd +stede +steds +steek +steem +steen +steil +stela +stele +stell +steme +stems +stend +steno +stens +stent +steps +stept +stere +stets +stews +stewy +steys +stich +stied +sties +stilb +stile +stime +stims +stimy +stipa +stipe +stire +stirk +stirp +stirs +stive +stivy +stoae +stoai +stoas +stoat +stobs +stoep +stogy +stoit +stoln +stoma +stond +stong +stonk +stonn +stook +stoor +stope +stops +stopt +stoss +stots +stott +stoun +stoup +stour +stown +stowp +stows +strad +strae +strag +strak +strep +strew +stria +strig +strim +strop +strow +stroy +strum +stubs +stude +studs +stull +stulm +stumm +stums +stuns +stupa +stupe +sture +sturt +styed +styes +styli +stylo +styme +stymy +styre +styte +subah +subas +subby +suber +subha +succi +sucks +sucky +sucre +sudds +sudor +sudsy +suede +suent +suers +suete +suets +suety +sugan +sughs +sugos +suhur +suids +suint +suits +sujee +sukhs +sukuk +sulci +sulfa +sulfo +sulks +sulph +sulus +sumis +summa +sumos +sumph +sumps +sunis +sunks +sunna +sunns +sunup +supes +supra +surah +sural +suras +surat +surds +sured +sures +surfs +surfy +surgy +surra +sused +suses +susus +sutor +sutra +sutta +swabs +swack +swads +swage +swags +swail +swain +swale +swaly +swamy +swang +swank +swans +swaps +swapt +sward +sware +swarf +swart +swats +swayl +sways +sweal +swede +sweed +sweel +sweer +swees +sweir +swelt +swerf +sweys +swies +swigs +swile +swims +swink +swipe +swire +swiss +swith +swits +swive +swizz +swobs +swole +swoln +swops +swopt +swots +swoun +sybbe +sybil +syboe +sybow +sycee +syces +sycon +syens +syker +sykes +sylis +sylph +sylva +symar +synch +syncs +synds +syned +synes +synth +syped +sypes +syphs +syrah +syren +sysop +sythe +syver +taals +taata +taber +tabes +tabid +tabis +tabla +tabor +tabun +tabus +tacan +taces +tacet +tache +tacho +tachs +tacks +tacos +tacts +taels +tafia +taggy +tagma +tahas +tahrs +taiga +taigs +taiko +tails +tains +taira +taish +taits +tajes +takas +takes +takhi +takin +takis +takky +talak +talaq +talar +talas +talcs +talcy +talea +taler +tales +talks +talky +talls +talma +talpa +taluk +talus +tamal +tamed +tames +tamin +tamis +tammy +tamps +tanas +tanga +tangi +tangs +tanhs +tanka +tanks +tanky +tanna +tansy +tanti +tanto +tanty +tapas +taped +tapen +tapes +tapet +tapis +tappa +tapus +taras +tardo +tared +tares +targa +targe +tarns +taroc +tarok +taros +tarps +tarre +tarry +tarsi +tarts +tarty +tasar +tased +taser +tases +tasks +tassa +tasse +tasso +tatar +tater +tates +taths +tatie +tatou +tatts +tatus +taube +tauld +tauon +taupe +tauts +tavah +tavas +taver +tawai +tawas +tawed +tawer +tawie +tawse +tawts +taxed +taxer +taxes +taxis +taxol +taxon +taxor +taxus +tayra +tazza +tazze +teade +teads +teaed +teaks +teals +teams +tears +teats +teaze +techs +techy +tecta +teels +teems +teend +teene +teens +teeny +teers +teffs +teggs +tegua +tegus +tehrs +teiid +teils +teind +teins +telae +telco +teles +telex +telia +telic +tells +telly +teloi +telos +temed +temes +tempi +temps +tempt +temse +tench +tends +tendu +tenes +tenge +tenia +tenne +tenno +tenny +tenon +tents +tenty +tenue +tepal +tepas +tepoy +terai +teras +terce +terek +teres +terfe +terfs +terga +terms +terne +terns +terry +terts +tesla +testa +teste +tests +tetes +teths +tetra +tetri +teuch +teugh +tewed +tewel +tewit +texas +texes +texts +thack +thagi +thaim +thale +thali +thana +thane +thang +thans +thanx +tharm +thars +thaws +thawy +thebe +theca +theed +theek +thees +thegn +theic +thein +thelf +thema +thens +theow +therm +thesp +thete +thews +thewy +thigs +thilk +thill +thine +thins +thiol +thirl +thoft +thole +tholi +thoro +thorp +thous +thowl +thrae +thraw +thrid +thrip +throe +thuds +thugs +thuja +thunk +thurl +thuya +thymi +thymy +tians +tiars +tical +ticca +ticed +tices +tichy +ticks +ticky +tiddy +tided +tides +tiers +tiffs +tifos +tifts +tiges +tigon +tikas +tikes +tikis +tikka +tilak +tiled +tiler +tiles +tills +tilly +tilth +tilts +timbo +timed +times +timon +timps +tinas +tinct +tinds +tinea +tined +tines +tinge +tings +tinks +tinny +tints +tinty +tipis +tippy +tired +tires +tirls +tiros +tirrs +titch +titer +titis +titre +titty +titup +tiyin +tiyns +tizes +tizzy +toads +toady +toaze +tocks +tocky +tocos +todde +toeas +toffs +toffy +tofts +tofus +togae +togas +toged +toges +togue +tohos +toile +toils +toing +toise +toits +tokay +toked +toker +tokes +tokos +tolan +tolar +tolas +toled +toles +tolls +tolly +tolts +tolus +tolyl +toman +tombs +tomes +tomia +tommy +tomos +tondi +tondo +toned +toner +tones +toney +tongs +tonka +tonks +tonne +tonus +tools +tooms +toons +toots +toped +topee +topek +toper +topes +tophe +tophi +tophs +topis +topoi +topos +toppy +toque +torah +toran +toras +torcs +tores +toric +torii +toros +torot +torrs +torse +torsi +torsk +torta +torte +torts +tosas +tosed +toses +toshy +tossy +toted +toter +totes +totty +touks +touns +tours +touse +tousy +touts +touze +touzy +towed +towie +towns +towny +towse +towsy +towts +towze +towzy +toyed +toyer +toyon +toyos +tozed +tozes +tozie +trabs +trads +tragi +traik +trams +trank +tranq +trans +trant +trape +traps +trapt +trass +trats +tratt +trave +trayf +trays +treck +treed +treen +trees +trefa +treif +treks +trema +trems +tress +trest +trets +trews +treyf +treys +triac +tride +trier +tries +triff +trigo +trigs +trike +trild +trill +trims +trine +trins +triol +trior +trios +trips +tripy +trist +troad +troak +troat +trock +trode +trods +trogs +trois +troke +tromp +trona +tronc +trone +tronk +trons +trooz +troth +trots +trows +troys +trued +trues +trugo +trugs +trull +tryer +tryke +tryma +tryps +tsade +tsadi +tsars +tsked +tsuba +tsubo +tuans +tuart +tuath +tubae +tubar +tubas +tubby +tubed +tubes +tucks +tufas +tuffe +tuffs +tufts +tufty +tugra +tuile +tuina +tuism +tuktu +tules +tulpa +tulsi +tumid +tummy +tumps +tumpy +tunas +tunds +tuned +tuner +tunes +tungs +tunny +tupek +tupik +tuple +tuque +turds +turfs +turfy +turks +turme +turms +turns +turnt +turps +turrs +tushy +tusks +tusky +tutee +tutti +tutty +tutus +tuxes +tuyer +twaes +twain +twals +twank +twats +tways +tweel +tween +tweep +tweer +twerk +twerp +twier +twigs +twill +twilt +twink +twins +twiny +twire +twirp +twite +twits +twoer +twyer +tyees +tyers +tyiyn +tykes +tyler +tymps +tynde +tyned +tynes +typal +typed +types +typey +typic +typos +typps +typto +tyran +tyred +tyres +tyros +tythe +tzars +udals +udons +ugali +ugged +uhlan +uhuru +ukase +ulama +ulans +ulema +ulmin +ulnad +ulnae +ulnar +ulnas +ulpan +ulvas +ulyie +ulzie +umami +umbel +umber +umble +umbos +umbre +umiac +umiak +umiaq +ummah +ummas +ummed +umped +umphs +umpie +umpty +umrah +umras +unais +unapt +unarm +unary +unaus +unbag +unban +unbar +unbed +unbid +unbox +uncap +unces +uncia +uncos +uncoy +uncus +undam +undee +undos +undug +uneth +unfix +ungag +unget +ungod +ungot +ungum +unhat +unhip +unica +units +unjam +unked +unket +unkid +unlaw +unlay +unled +unlet +unlid +unman +unmew +unmix +unpay +unpeg +unpen +unpin +unred +unrid +unrig +unrip +unsaw +unsay +unsee +unsew +unsex +unsod +untax +untin +unwet +unwit +unwon +upbow +upbye +updos +updry +upend +upjet +uplay +upled +uplit +upped +upran +uprun +upsee +upsey +uptak +upter +uptie +uraei +urali +uraos +urare +urari +urase +urate +urbex +urbia +urdee +ureal +ureas +uredo +ureic +urena +urent +urged +urger +urges +urial +urite +urman +urnal +urned +urped +ursae +ursid +urson +urubu +urvas +users +usnea +usque +usure +usury +uteri +uveal +uveas +uvula +vacua +vaded +vades +vagal +vagus +vails +vaire +vairs +vairy +vakas +vakil +vales +valis +valse +vamps +vampy +vanda +vaned +vanes +vangs +vants +vaped +vaper +vapes +varan +varas +vardy +varec +vares +varia +varix +varna +varus +varve +vasal +vases +vasts +vasty +vatic +vatus +vauch +vaute +vauts +vawte +vaxes +veale +veals +vealy +veena +veeps +veers +veery +vegas +veges +vegie +vegos +vehme +veils +veily +veins +veiny +velar +velds +veldt +veles +vells +velum +venae +venal +vends +vendu +veney +venge +venin +vents +venus +verbs +verra +verry +verst +verts +vertu +vespa +vesta +vests +vetch +vexed +vexer +vexes +vexil +vezir +vials +viand +vibes +vibex +vibey +viced +vices +vichy +viers +views +viewy +vifda +viffs +vigas +vigia +vilde +viler +villi +vills +vimen +vinal +vinas +vinca +vined +viner +vines +vinew +vinic +vinos +vints +viold +viols +vired +vireo +vires +virga +virge +virid +virls +virtu +visas +vised +vises +visie +visne +vison +visto +vitae +vitas +vitex +vitro +vitta +vivas +vivat +vivda +viver +vives +vizir +vizor +vleis +vlies +vlogs +voars +vocab +voces +voddy +vodou +vodun +voema +vogie +voids +voile +voips +volae +volar +voled +voles +volet +volks +volta +volte +volti +volts +volva +volve +vomer +voted +votes +vouge +voulu +vowed +vower +voxel +vozhd +vraic +vrils +vroom +vrous +vrouw +vrows +vuggs +vuggy +vughs +vughy +vulgo +vulns +vulva +vutty +waacs +wacke +wacko +wacks +wadds +waddy +waded +wader +wades +wadge +wadis +wadts +waffs +wafts +waged +wages +wagga +wagyu +wahoo +waide +waifs +waift +wails +wains +wairs +waite +waits +wakas +waked +waken +waker +wakes +wakfs +waldo +walds +waled +waler +wales +walie +walis +walks +walla +walls +wally +walty +wamed +wames +wamus +wands +waned +wanes +waney +wangs +wanks +wanky +wanle +wanly +wanna +wants +wanty +wanze +waqfs +warbs +warby +wards +wared +wares +warez +warks +warms +warns +warps +warre +warst +warts +wases +washy +wasms +wasps +waspy +wasts +watap +watts +wauff +waugh +wauks +waulk +wauls +waurs +waved +waves +wavey +wawas +wawes +wawls +waxed +waxer +waxes +wayed +wazir +wazoo +weald +weals +weamb +weans +wears +webby +weber +wecht +wedel +wedgy +weeds +weeke +weeks +weels +weems +weens +weeny +weeps +weepy +weest +weete +weets +wefte +wefts +weids +weils +weirs +weise +weize +wekas +welds +welke +welks +welkt +wells +welly +welts +wembs +wends +wenge +wenny +wents +weros +wersh +wests +wetas +wetly +wexed +wexes +whamo +whams +whang +whaps +whare +whata +whats +whaup +whaur +wheal +whear +wheen +wheep +wheft +whelk +whelm +whens +whets +whews +wheys +whids +whift +whigs +whilk +whims +whins +whios +whips +whipt +whirr +whirs +whish +whiss +whist +whits +whity +whizz +whomp +whoof +whoot +whops +whore +whorl +whort +whoso +whows +whump +whups +whyda +wicca +wicks +wicky +widdy +wides +wiels +wifed +wifes +wifey +wifie +wifty +wigan +wigga +wiggy +wikis +wilco +wilds +wiled +wiles +wilga +wilis +wilja +wills +wilts +wimps +winds +wined +wines +winey +winge +wings +wingy +winks +winna +winns +winos +winze +wiped +wiper +wipes +wired +wirer +wires +wirra +wised +wises +wisha +wisht +wisps +wists +witan +wited +wites +withe +withs +withy +wived +wiver +wives +wizen +wizes +woads +woald +wocks +wodge +woful +wojus +woker +wokka +wolds +wolfs +wolly +wolve +wombs +womby +womyn +wonga +wongi +wonks +wonky +wonts +woods +wooed +woofs +woofy +woold +wools +woons +woops +woopy +woose +woosh +wootz +words +works +worms +wormy +worts +wowed +wowee +woxen +wrang +wraps +wrapt +wrast +wrate +wrawl +wrens +wrick +wried +wrier +wries +writs +wroke +wroot +wroth +wryer +wuddy +wudus +wulls +wurst +wuses +wushu +wussy +wuxia +wyled +wyles +wynds +wynns +wyted +wytes +xebec +xenia +xenic +xenon +xeric +xerox +xerus +xoana +xrays +xylan +xylem +xylic +xylol +xylyl +xysti +xysts +yaars +yabas +yabba +yabby +yacca +yacka +yacks +yaffs +yager +yages +yagis +yahoo +yaird +yakka +yakow +yales +yamen +yampy +yamun +yangs +yanks +yapok +yapon +yapps +yappy +yarak +yarco +yards +yarer +yarfa +yarks +yarns +yarrs +yarta +yarto +yates +yauds +yauld +yaups +yawed +yawey +yawls +yawns +yawny +yawps +ybore +yclad +ycled +ycond +ydrad +ydred +yeads +yeahs +yealm +yeans +yeard +years +yecch +yechs +yechy +yedes +yeeds +yeesh +yeggs +yelks +yells +yelms +yelps +yelts +yenta +yente +yerba +yerds +yerks +yeses +yesks +yests +yesty +yetis +yetts +yeuks +yeuky +yeven +yeves +yewen +yexed +yexes +yfere +yiked +yikes +yills +yince +yipes +yippy +yirds +yirks +yirrs +yirth +yites +yitie +ylems +ylike +ylkes +ymolt +ympes +yobbo +yobby +yocks +yodel +yodhs +yodle +yogas +yogee +yoghs +yogic +yogin +yogis +yoick +yojan +yoked +yokel +yoker +yokes +yokul +yolks +yolky +yomim +yomps +yonic +yonis +yonks +yoofs +yoops +yores +yorks +yorps +youks +yourn +yours +yourt +youse +yowed +yowes +yowie +yowls +yowza +yrapt +yrent +yrivd +yrneh +ysame +ytost +yuans +yucas +yucca +yucch +yucko +yucks +yucky +yufts +yugas +yuked +yukes +yukky +yukos +yulan +yules +yummo +yummy +yumps +yupon +yuppy +yurta +yurts +yuzus +zabra +zacks +zaida +zaidy +zaire +zakat +zaman +zambo +zamia +zanja +zante +zanza +zanze +zappy +zarfs +zaris +zatis +zaxes +zayin +zazen +zeals +zebec +zebub +zebus +zedas +zeins +zendo +zerda +zerks +zeros +zests +zetas +zexes +zezes +zhomo +zibet +ziffs +zigan +zilas +zilch +zilla +zills +zimbi +zimbs +zinco +zincs +zincy +zineb +zines +zings +zingy +zinke +zinky +zippo +zippy +ziram +zitis +zizel +zizit +zlote +zloty +zoaea +zobos +zobus +zocco +zoeae +zoeal +zoeas +zoism +zoist +zombi +zonae +zonda +zoned +zoner +zones +zonks +zooea +zooey +zooid +zooks +zooms +zoons +zooty +zoppa +zoppo +zoril +zoris +zorro +zouks +zowee +zowie +zulus +zupan +zupas +zuppa +zurfs +zuzim +zygal +zygon +zymes +zymic \ No newline at end of file diff --git a/web/samples/WordWiz/src/assets/allowedWords.ts b/web/samples/WordWiz/src/assets/allowedWords.ts new file mode 100644 index 0000000000..dc074ea1fd --- /dev/null +++ b/web/samples/WordWiz/src/assets/allowedWords.ts @@ -0,0 +1,12 @@ +// Convert allowed.txt into an array of words for use across the app +// The file is imported as raw text via Vite's ?raw import. +import allowedRaw from '@app/assets/allowed.txt?raw'; + +// Normalize: trim, filter empty, and lowercase (dictionary is case-insensitive) +export const allowedWords: string[] = allowedRaw + .split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line.length > 0) + .map((line) => line.toLowerCase()); + +export default allowedWords; diff --git a/web/samples/WordWiz/src/assets/answers.ts b/web/samples/WordWiz/src/assets/answers.ts new file mode 100644 index 0000000000..c52c91e1a6 --- /dev/null +++ b/web/samples/WordWiz/src/assets/answers.ts @@ -0,0 +1,12 @@ +// Convert answers.txt into an array of words for use across the app +// The file is imported as raw text via Vite's ?raw import. +import answersRaw from '@app/assets/answers.txt?raw'; + +// Normalize: trim, filter empty, and lowercase (dictionary is case-insensitive) +export const answers: string[] = answersRaw + .split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line.length > 0) + .map((line) => line.toLowerCase()); + +export default answers; diff --git a/web/samples/WordWiz/src/assets/answers.txt b/web/samples/WordWiz/src/assets/answers.txt new file mode 100644 index 0000000000..50838083dc --- /dev/null +++ b/web/samples/WordWiz/src/assets/answers.txt @@ -0,0 +1,2315 @@ +aback +abase +abate +abbey +abbot +abhor +abide +abled +abode +abort +about +above +abuse +abyss +acorn +acrid +actor +acute +adage +adapt +adept +admin +admit +adobe +adopt +adore +adorn +adult +affix +afire +afoot +afoul +after +again +agape +agate +agent +agile +aging +aglow +agony +agora +agree +ahead +aider +aisle +alarm +album +alert +algae +alibi +alien +align +alike +alive +allay +alley +allot +allow +alloy +aloft +alone +along +aloof +aloud +alpha +altar +alter +amass +amaze +amber +amble +amend +amiss +amity +among +ample +amply +amuse +angel +anger +angle +angry +angst +anime +ankle +annex +annoy +annul +anode +antic +anvil +aorta +apart +aphid +aping +apnea +apple +apply +apron +aptly +arbor +ardor +arena +argue +arise +armor +aroma +arose +array +arrow +arson +artsy +ascot +ashen +aside +askew +assay +asset +atoll +atone +attic +audio +audit +augur +aunty +avail +avert +avian +avoid +await +awake +award +aware +awash +awful +awoke +axial +axiom +axion +azure +bacon +badge +badly +bagel +baggy +baker +baler +balmy +banal +banjo +barge +baron +basal +basic +basil +basin +basis +baste +batch +bathe +baton +batty +bawdy +bayou +beach +beady +beard +beast +beech +beefy +befit +began +begat +beget +begin +begun +being +belch +belie +belle +belly +below +bench +beret +berry +berth +beset +betel +bevel +bezel +bible +bicep +biddy +bigot +bilge +billy +binge +bingo +biome +birch +birth +bison +bitty +black +blade +blame +bland +blank +blare +blast +blaze +bleak +bleat +bleed +bleep +blend +bless +blimp +blind +blink +bliss +blitz +bloat +block +bloke +blond +blood +bloom +blown +bluer +bluff +blunt +blurb +blurt +blush +board +boast +bobby +boney +bongo +bonus +booby +boost +booth +booty +booze +boozy +borax +borne +bosom +bossy +botch +bough +boule +bound +bowel +boxer +brace +braid +brain +brake +brand +brash +brass +brave +bravo +brawl +brawn +bread +break +breed +briar +bribe +brick +bride +brief +brine +bring +brink +briny +brisk +broad +broil +broke +brood +brook +broom +broth +brown +brunt +brush +brute +buddy +budge +buggy +bugle +build +built +bulge +bulky +bully +bunch +bunny +burly +burnt +burst +bused +bushy +butch +butte +buxom +buyer +bylaw +cabal +cabby +cabin +cable +cacao +cache +cacti +caddy +cadet +cagey +cairn +camel +cameo +canal +candy +canny +canoe +canon +caper +caput +carat +cargo +carol +carry +carve +caste +catch +cater +catty +caulk +cause +cavil +cease +cedar +cello +chafe +chaff +chain +chair +chalk +champ +chant +chaos +chard +charm +chart +chase +chasm +cheap +cheat +check +cheek +cheer +chess +chest +chick +chide +chief +child +chili +chill +chime +china +chirp +chock +choir +choke +chord +chore +chose +chuck +chump +chunk +churn +chute +cider +cigar +cinch +circa +civic +civil +clack +claim +clamp +clang +clank +clash +clasp +class +clean +clear +cleat +cleft +clerk +click +cliff +climb +cling +clink +cloak +clock +clone +close +cloth +cloud +clout +clove +clown +cluck +clued +clump +clung +coach +coast +cobra +cocoa +colon +color +comet +comfy +comic +comma +conch +condo +conic +copse +coral +corer +corny +couch +cough +could +count +coupe +court +coven +cover +covet +covey +cower +coyly +crack +craft +cramp +crane +crank +crash +crass +crate +crave +crawl +craze +crazy +creak +cream +credo +creed +creek +creep +creme +crepe +crept +cress +crest +crick +cried +crier +crime +crimp +crisp +croak +crock +crone +crony +crook +cross +croup +crowd +crown +crude +cruel +crumb +crump +crush +crust +crypt +cubic +cumin +curio +curly +curry +curse +curve +curvy +cutie +cyber +cycle +cynic +daddy +daily +dairy +daisy +dally +dance +dandy +datum +daunt +dealt +death +debar +debit +debug +debut +decal +decay +decor +decoy +decry +defer +deign +deity +delay +delta +delve +demon +demur +denim +dense +depot +depth +derby +deter +detox +deuce +devil +diary +dicey +digit +dilly +dimly +diner +dingo +dingy +diode +dirge +dirty +disco +ditch +ditto +ditty +diver +dizzy +dodge +dodgy +dogma +doing +dolly +donor +donut +dopey +doubt +dough +dowdy +dowel +downy +dowry +dozen +draft +drain +drake +drama +drank +drape +drawl +drawn +dread +dream +dress +dried +drier +drift +drill +drink +drive +droit +droll +drone +drool +droop +dross +drove +drown +druid +drunk +dryer +dryly +duchy +dully +dummy +dumpy +dunce +dusky +dusty +dutch +duvet +dwarf +dwell +dwelt +dying +eager +eagle +early +earth +easel +eaten +eater +ebony +eclat +edict +edify +eerie +egret +eight +eject +eking +elate +elbow +elder +elect +elegy +elfin +elide +elite +elope +elude +email +embed +ember +emcee +empty +enact +endow +enema +enemy +enjoy +ennui +ensue +enter +entry +envoy +epoch +epoxy +equal +equip +erase +erect +erode +error +erupt +essay +ester +ether +ethic +ethos +etude +evade +event +every +evict +evoke +exact +exalt +excel +exert +exile +exist +expel +extol +extra +exult +eying +fable +facet +faint +fairy +faith +false +fancy +fanny +farce +fatal +fatty +fault +fauna +favor +feast +fecal +feign +fella +felon +femme +femur +fence +feral +ferry +fetal +fetch +fetid +fetus +fever +fewer +fiber +fibre +ficus +field +fiend +fiery +fifth +fifty +fight +filer +filet +filly +filmy +filth +final +finch +finer +first +fishy +fixer +fizzy +fjord +flack +flail +flair +flake +flaky +flame +flank +flare +flash +flask +fleck +fleet +flesh +flick +flier +fling +flint +flirt +float +flock +flood +floor +flora +floss +flour +flout +flown +fluff +fluid +fluke +flume +flung +flunk +flush +flute +flyer +foamy +focal +focus +foggy +foist +folio +folly +foray +force +forge +forgo +forte +forth +forty +forum +found +foyer +frail +frame +frank +fraud +freak +freed +freer +fresh +friar +fried +frill +frisk +fritz +frock +frond +front +frost +froth +frown +froze +fruit +fudge +fugue +fully +fungi +funky +funny +furor +furry +fussy +fuzzy +gaffe +gaily +gamer +gamma +gamut +gassy +gaudy +gauge +gaunt +gauze +gavel +gawky +gayer +gayly +gazer +gecko +geeky +geese +genie +genre +ghost +ghoul +giant +giddy +gipsy +girly +girth +given +giver +glade +gland +glare +glass +glaze +gleam +glean +glide +glint +gloat +globe +gloom +glory +gloss +glove +glyph +gnash +gnome +godly +going +golem +golly +gonad +goner +goody +gooey +goofy +goose +gorge +gouge +gourd +grace +grade +graft +grail +grain +grand +grant +grape +graph +grasp +grass +grate +grave +gravy +graze +great +greed +green +greet +grief +grill +grime +grimy +grind +gripe +groan +groin +groom +grope +gross +group +grout +grove +growl +grown +gruel +gruff +grunt +guard +guava +guess +guest +guide +guild +guile +guilt +guise +gulch +gully +gumbo +gummy +guppy +gusto +gusty +gypsy +habit +hairy +halve +handy +happy +hardy +harem +harpy +harry +harsh +haste +hasty +hatch +hater +haunt +haute +haven +havoc +hazel +heady +heard +heart +heath +heave +heavy +hedge +hefty +heist +helix +hello +hence +heron +hilly +hinge +hippo +hippy +hitch +hoard +hobby +hoist +holly +homer +honey +honor +horde +horny +horse +hotel +hotly +hound +house +hovel +hover +howdy +human +humid +humor +humph +humus +hunch +hunky +hurry +husky +hussy +hutch +hydro +hyena +hymen +hyper +icily +icing +ideal +idiom +idiot +idler +idyll +igloo +iliac +image +imbue +impel +imply +inane +inbox +incur +index +inept +inert +infer +ingot +inlay +inlet +inner +input +inter +intro +ionic +irate +irony +islet +issue +itchy +ivory +jaunt +jazzy +jelly +jerky +jetty +jewel +jiffy +joint +joist +joker +jolly +joust +judge +juice +juicy +jumbo +jumpy +junta +junto +juror +kappa +karma +kayak +kebab +khaki +kinky +kiosk +kitty +knack +knave +knead +kneed +kneel +knelt +knife +knock +knoll +known +koala +krill +label +labor +laden +ladle +lager +lance +lanky +lapel +lapse +large +larva +lasso +latch +later +lathe +latte +laugh +layer +leach +leafy +leaky +leant +leapt +learn +lease +leash +least +leave +ledge +leech +leery +lefty +legal +leggy +lemon +lemur +leper +level +lever +libel +liege +light +liken +lilac +limbo +limit +linen +liner +lingo +lipid +lithe +liver +livid +llama +loamy +loath +lobby +local +locus +lodge +lofty +logic +login +loopy +loose +lorry +loser +louse +lousy +lover +lower +lowly +loyal +lucid +lucky +lumen +lumpy +lunar +lunch +lunge +lupus +lurch +lurid +lusty +lying +lymph +lynch +lyric +macaw +macho +macro +madam +madly +mafia +magic +magma +maize +major +maker +mambo +mamma +mammy +manga +mange +mango +mangy +mania +manic +manly +manor +maple +march +marry +marsh +mason +masse +match +matey +mauve +maxim +maybe +mayor +mealy +meant +meaty +mecca +medal +media +medic +melee +melon +mercy +merge +merit +merry +metal +meter +metro +micro +midge +midst +might +milky +mimic +mince +miner +minim +minor +minty +minus +mirth +miser +missy +mocha +modal +model +modem +mogul +moist +molar +moldy +money +month +moody +moose +moral +moron +morph +mossy +motel +motif +motor +motto +moult +mound +mount +mourn +mouse +mouth +mover +movie +mower +mucky +mucus +muddy +mulch +mummy +munch +mural +murky +mushy +music +musky +musty +myrrh +nadir +naive +nanny +nasal +nasty +natal +naval +navel +needy +neigh +nerdy +nerve +never +newer +newly +nicer +niche +niece +night +ninja +ninny +ninth +noble +nobly +noise +noisy +nomad +noose +north +nosey +notch +novel +nudge +nurse +nutty +nylon +nymph +oaken +obese +occur +ocean +octal +octet +odder +oddly +offal +offer +often +olden +older +olive +ombre +omega +onion +onset +opera +opine +opium +optic +orbit +order +organ +other +otter +ought +ounce +outdo +outer +outgo +ovary +ovate +overt +ovine +ovoid +owing +owner +oxide +ozone +paddy +pagan +paint +paler +palsy +panel +panic +pansy +papal +paper +parer +parka +parry +parse +party +pasta +paste +pasty +patch +patio +patsy +patty +pause +payee +payer +peace +peach +pearl +pecan +pedal +penal +pence +penne +penny +perch +peril +perky +pesky +pesto +petal +petty +phase +phone +phony +photo +piano +picky +piece +piety +piggy +pilot +pinch +piney +pinky +pinto +piper +pique +pitch +pithy +pivot +pixel +pixie +pizza +place +plaid +plain +plait +plane +plank +plant +plate +plaza +plead +pleat +plied +plier +pluck +plumb +plume +plump +plunk +plush +poesy +point +poise +poker +polar +polka +polyp +pooch +poppy +porch +poser +posit +posse +pouch +pound +pouty +power +prank +prawn +preen +press +price +prick +pride +pried +prime +primo +print +prior +prism +privy +prize +probe +prone +prong +proof +prose +proud +prove +prowl +proxy +prude +prune +psalm +pubic +pudgy +puffy +pulpy +pulse +punch +pupal +pupil +puppy +puree +purer +purge +purse +pushy +putty +pygmy +quack +quail +quake +qualm +quark +quart +quash +quasi +queen +queer +quell +query +quest +queue +quick +quiet +quill +quilt +quirk +quite +quota +quote +quoth +rabbi +rabid +racer +radar +radii +radio +rainy +raise +rajah +rally +ralph +ramen +ranch +randy +range +rapid +rarer +raspy +ratio +ratty +raven +rayon +razor +reach +react +ready +realm +rearm +rebar +rebel +rebus +rebut +recap +recur +recut +reedy +refer +refit +regal +rehab +reign +relax +relay +relic +remit +renal +renew +repay +repel +reply +rerun +reset +resin +retch +retro +retry +reuse +revel +revue +rhino +rhyme +rider +ridge +rifle +right +rigid +rigor +rinse +ripen +riper +risen +riser +risky +rival +river +rivet +roach +roast +robin +robot +rocky +rodeo +roger +rogue +roomy +roost +rotor +rouge +rough +round +rouse +route +rover +rowdy +rower +royal +ruddy +ruder +rugby +ruler +rumba +rumor +rupee +rural +rusty +sadly +safer +saint +salad +sally +salon +salsa +salty +salve +salvo +sandy +saner +sappy +sassy +satin +satyr +sauce +saucy +sauna +saute +savor +savoy +savvy +scald +scale +scalp +scaly +scamp +scant +scare +scarf +scary +scene +scent +scion +scoff +scold +scone +scoop +scope +score +scorn +scour +scout +scowl +scram +scrap +scree +screw +scrub +scrum +scuba +sedan +seedy +segue +seize +semen +sense +sepia +serif +serum +serve +setup +seven +sever +sewer +shack +shade +shady +shaft +shake +shaky +shale +shall +shalt +shame +shank +shape +shard +share +shark +sharp +shave +shawl +shear +sheen +sheep +sheer +sheet +sheik +shelf +shell +shied +shift +shine +shiny +shire +shirk +shirt +shoal +shock +shone +shook +shoot +shore +shorn +short +shout +shove +shown +showy +shrew +shrub +shrug +shuck +shunt +shush +shyly +siege +sieve +sight +sigma +silky +silly +since +sinew +singe +siren +sissy +sixth +sixty +skate +skier +skiff +skill +skimp +skirt +skulk +skull +skunk +slack +slain +slang +slant +slash +slate +slave +sleek +sleep +sleet +slept +slice +slick +slide +slime +slimy +sling +slink +sloop +slope +slosh +sloth +slump +slung +slunk +slurp +slush +slyly +smack +small +smart +smash +smear +smell +smelt +smile +smirk +smite +smith +smock +smoke +smoky +smote +snack +snail +snake +snaky +snare +snarl +sneak +sneer +snide +sniff +snipe +snoop +snore +snort +snout +snowy +snuck +snuff +soapy +sober +soggy +solar +solid +solve +sonar +sonic +sooth +sooty +sorry +sound +south +sower +space +spade +spank +spare +spark +spasm +spawn +speak +spear +speck +speed +spell +spelt +spend +spent +sperm +spice +spicy +spied +spiel +spike +spiky +spill +spilt +spine +spiny +spire +spite +splat +split +spoil +spoke +spoof +spook +spool +spoon +spore +sport +spout +spray +spree +sprig +spunk +spurn +spurt +squad +squat +squib +stack +staff +stage +staid +stain +stair +stake +stale +stalk +stall +stamp +stand +stank +stare +stark +start +stash +state +stave +stead +steak +steal +steam +steed +steel +steep +steer +stein +stern +stick +stiff +still +stilt +sting +stink +stint +stock +stoic +stoke +stole +stomp +stone +stony +stood +stool +stoop +store +stork +storm +story +stout +stove +strap +straw +stray +strip +strut +stuck +study +stuff +stump +stung +stunk +stunt +style +suave +sugar +suing +suite +sulky +sully +sumac +sunny +super +surer +surge +surly +sushi +swami +swamp +swarm +swash +swath +swear +sweat +sweep +sweet +swell +swept +swift +swill +swine +swing +swirl +swish +swoon +swoop +sword +swore +sworn +swung +synod +syrup +tabby +table +taboo +tacit +tacky +taffy +taint +taken +taker +tally +talon +tamer +tango +tangy +taper +tapir +tardy +tarot +taste +tasty +tatty +taunt +tawny +teach +teary +tease +teddy +teeth +tempo +tenet +tenor +tense +tenth +tepee +tepid +terra +terse +testy +thank +theft +their +theme +there +these +theta +thick +thief +thigh +thing +think +third +thong +thorn +those +three +threw +throb +throw +thrum +thumb +thump +thyme +tiara +tibia +tidal +tiger +tight +tilde +timer +timid +tipsy +titan +tithe +title +toast +today +toddy +token +tonal +tonga +tonic +tooth +topaz +topic +torch +torso +torus +total +totem +touch +tough +towel +tower +toxic +toxin +trace +track +tract +trade +trail +train +trait +tramp +trash +trawl +tread +treat +trend +triad +trial +tribe +trice +trick +tried +tripe +trite +troll +troop +trope +trout +trove +truce +truck +truer +truly +trump +trunk +truss +trust +truth +tryst +tubal +tuber +tulip +tulle +tumor +tunic +turbo +tutor +twang +tweak +tweed +tweet +twice +twine +twirl +twist +twixt +tying +udder +ulcer +ultra +umbra +uncle +uncut +under +undid +undue +unfed +unfit +unify +union +unite +unity +unlit +unmet +unset +untie +until +unwed +unzip +upper +upset +urban +urine +usage +usher +using +usual +usurp +utile +utter +vague +valet +valid +valor +value +valve +vapid +vapor +vault +vaunt +vegan +venom +venue +verge +verse +verso +verve +vicar +video +vigil +vigor +villa +vinyl +viola +viper +viral +virus +visit +visor +vista +vital +vivid +vixen +vocal +vodka +vogue +voice +voila +vomit +voter +vouch +vowel +vying +wacky +wafer +wager +wagon +waist +waive +waltz +warty +waste +watch +water +waver +waxen +weary +weave +wedge +weedy +weigh +weird +welch +welsh +wench +whack +whale +wharf +wheat +wheel +whelp +where +which +whiff +while +whine +whiny +whirl +whisk +white +whole +whoop +whose +widen +wider +widow +width +wield +wight +willy +wimpy +wince +winch +windy +wiser +wispy +witch +witty +woken +woman +women +woody +wooer +wooly +woozy +wordy +world +worry +worse +worst +worth +would +wound +woven +wrack +wrath +wreak +wreck +wrest +wring +wrist +write +wrong +wrote +wrung +wryly +yacht +yearn +yeast +yield +young +youth +zebra +zesty +zonal \ No newline at end of file diff --git a/web/samples/WordWiz/src/assets/logo.png b/web/samples/WordWiz/src/assets/logo.png new file mode 100644 index 0000000000..1251323791 Binary files /dev/null and b/web/samples/WordWiz/src/assets/logo.png differ diff --git a/web/samples/WordWiz/src/beam.ts b/web/samples/WordWiz/src/beam.ts new file mode 100644 index 0000000000..d7bd60f0c5 --- /dev/null +++ b/web/samples/WordWiz/src/beam.ts @@ -0,0 +1,53 @@ +import { AccountService, AuthService, Beam, StatsService } from 'beamable-sdk'; +import { DAILY_STREAK, ENDLESS_STREAK } from '@app/game/constants.ts'; +import { GameStore } from '@app/game/state/store.ts'; + +/** + * Initializes Beamable SDK + * - Configures Beam with environment values. + * - Registers required Beam services. + * + * @returns {Promise} A fully initialized and authenticated Beam instance. + */ +export async function setupBeam(): Promise { + // Initialize Beamable SDK with project configuration + const beam: Beam = await Beam.init({ + cid: '1639786776798208', + pid: 'DE_1912686680736768', + environment: 'dev', + }); + + try { + // Register required services with the Beam instance + beam.use(AuthService).use(AccountService).use(StatsService); + return beam; + } catch (error) { + console.error('Beam Error:', error); + return beam; + } +} + +export async function getAndComputePlayerStats( + beam: Beam, + store: GameStore, +): Promise { + try { + const stats = await beam.stats.get({ + accessType: 'private', + stats: [DAILY_STREAK, ENDLESS_STREAK], + }); + + const dailyStreak = DAILY_STREAK in stats ? stats[DAILY_STREAK] : '0'; + const endlessStreak = + ENDLESS_STREAK in stats ? stats[ENDLESS_STREAK] : '0'; + + store.stats = { + [DAILY_STREAK]: dailyStreak, + [ENDLESS_STREAK]: endlessStreak, + }; + + dispatchEvent(new CustomEvent('stats_updated')); + } catch (error) { + console.error('Failed to fetch player stats:', error); + } +} diff --git a/web/samples/WordWiz/src/components/App.tsx b/web/samples/WordWiz/src/components/App.tsx new file mode 100644 index 0000000000..351574a493 --- /dev/null +++ b/web/samples/WordWiz/src/components/App.tsx @@ -0,0 +1,15 @@ +import { Navigate, Route, Routes, HashRouter } from 'react-router-dom'; +import { routes } from '@app/navigation/routes.tsx'; + +export function App() { + return ( + + + {routes.map((route) => ( + + ))} + } /> + + + ); +} diff --git a/web/samples/WordWiz/src/components/Avatar.tsx b/web/samples/WordWiz/src/components/Avatar.tsx new file mode 100644 index 0000000000..d229a32b17 --- /dev/null +++ b/web/samples/WordWiz/src/components/Avatar.tsx @@ -0,0 +1,40 @@ +type AvatarProps = { + name?: string; + picture?: string; + size?: number; // pixels + className?: string; +}; + +export const Avatar = ({ + name = 'Guest', + picture = '', + size = 32, + className = '', +}: AvatarProps) => { + const initial = (name?.trim?.() || 'G').charAt(0).toUpperCase(); + + const style: React.CSSProperties = { + width: size, + height: size, + fontSize: Math.round(size * 0.45), + }; + + return ( +
+ {picture ? ( + {name} + ) : ( + <>{initial} + )} +
+ ); +}; diff --git a/web/samples/WordWiz/src/components/ErrorBoundary.tsx b/web/samples/WordWiz/src/components/ErrorBoundary.tsx new file mode 100644 index 0000000000..bcc3fa68b0 --- /dev/null +++ b/web/samples/WordWiz/src/components/ErrorBoundary.tsx @@ -0,0 +1,48 @@ +import { + Component, + type ComponentType, + type GetDerivedStateFromError, + type PropsWithChildren, + type ReactNode, +} from 'react'; + +export interface ErrorBoundaryProps extends PropsWithChildren { + fallback?: ReactNode | ComponentType<{ error: unknown }>; +} + +interface ErrorBoundaryState { + error?: unknown; +} + +export class ErrorBoundary extends Component< + ErrorBoundaryProps, + ErrorBoundaryState +> { + state: ErrorBoundaryState = {}; + + static getDerivedStateFromError: GetDerivedStateFromError< + ErrorBoundaryProps, + ErrorBoundaryState + > = (error) => ({ error }); + + componentDidCatch(error: Error) { + this.setState({ error }); + } + + render() { + const { + state: { error }, + props: { fallback: Fallback, children }, + } = this; + + return 'error' in this.state ? ( + typeof Fallback === 'function' ? ( + + ) : ( + Fallback + ) + ) : ( + children + ); + } +} diff --git a/web/samples/WordWiz/src/components/Grid.tsx b/web/samples/WordWiz/src/components/Grid.tsx new file mode 100644 index 0000000000..ede17ee508 --- /dev/null +++ b/web/samples/WordWiz/src/components/Grid.tsx @@ -0,0 +1,35 @@ +import { MAX_ATTEMPTS, TIPS, WORD_LENGTH } from '@app/game/constants.ts'; +import { useGrid } from '@app/components/hooks/useGrid.ts'; + +export const Grid = () => { + const { tipTextRef, setTilesRef, setGridRowsRef } = useGrid(); + + return ( +
+
+ {Array.from({ length: MAX_ATTEMPTS }, (_, row) => { + return ( +
+ {Array.from({ length: WORD_LENGTH }).map((_, col) => ( +
+ ))} +
+ ); + })} +
+

+ {TIPS} +

+
+ ); +}; diff --git a/web/samples/WordWiz/src/components/Header.tsx b/web/samples/WordWiz/src/components/Header.tsx new file mode 100644 index 0000000000..4f17d462b4 --- /dev/null +++ b/web/samples/WordWiz/src/components/Header.tsx @@ -0,0 +1,25 @@ +import { Avatar } from '@app/components/Avatar.tsx'; +import { useHeader } from '@app/components/hooks/useHeader.ts'; +import { DAILY_STREAK, ENDLESS_STREAK } from '@app/game/constants.ts'; + +export const Header = () => { + const { state, stats } = useHeader(); + + return ( +
+
+
+ +

Guest

+
+
+

{state.mode}

+

+ Streak:{' '} + {stats[state.mode === 'daily' ? DAILY_STREAK : ENDLESS_STREAK]} +

+
+
+
+ ); +}; diff --git a/web/samples/WordWiz/src/components/Keyboard.tsx b/web/samples/WordWiz/src/components/Keyboard.tsx new file mode 100644 index 0000000000..eb3b2c4cfb --- /dev/null +++ b/web/samples/WordWiz/src/components/Keyboard.tsx @@ -0,0 +1,90 @@ +import { useKeyboard } from '@app/components/hooks/useKeyboard.ts'; +import { KEYBOARD_ROW } from '@app/game/constants.ts'; + +export const Keyboard = () => { + const { + setKeyboardKeysRef, + handleOnScreenKeyboardKeyClick, + handleSubmit, + handleDeleteLetter, + } = useKeyboard(); + + return ( +
+
+
+ {KEYBOARD_ROW[0].map((key) => { + return ( + + ); + })} +
+ +
+
+ {KEYBOARD_ROW[1].map((key) => { + return ( + + ); + })} +
+
+ +
+ + {KEYBOARD_ROW[2].map((key) => { + return ( + + ); + })} + +
+
+
+ ); +}; diff --git a/web/samples/WordWiz/src/components/Page.tsx b/web/samples/WordWiz/src/components/Page.tsx new file mode 100644 index 0000000000..45ce1e4e6f --- /dev/null +++ b/web/samples/WordWiz/src/components/Page.tsx @@ -0,0 +1,5 @@ +import { type PropsWithChildren } from 'react'; + +export function Page({ children }: PropsWithChildren) { + return <>{children}; +} diff --git a/web/samples/WordWiz/src/components/Profile.tsx b/web/samples/WordWiz/src/components/Profile.tsx new file mode 100644 index 0000000000..b87671a33c --- /dev/null +++ b/web/samples/WordWiz/src/components/Profile.tsx @@ -0,0 +1,19 @@ +import { Avatar } from '@app/components/Avatar.tsx'; + +interface ProfileProps { + name?: string; + picture?: string; +} + +export const Profile = ({ name = 'Guest', picture = '' }: ProfileProps) => { + return ( +
+
+
+ +

{name}

+
+
+
+ ); +}; diff --git a/web/samples/WordWiz/src/components/Result.tsx b/web/samples/WordWiz/src/components/Result.tsx new file mode 100644 index 0000000000..ffa2177639 --- /dev/null +++ b/web/samples/WordWiz/src/components/Result.tsx @@ -0,0 +1,54 @@ +import logo from '@app/assets/logo.png'; +import { useResult } from '@app/components/hooks/useResult.ts'; +import HomeIcon from '@app/assets/HomeIcon.tsx'; +import NextIcon from '@app/assets/NextIcon.tsx'; + +export const Result = () => { + const { + state, + didWin, + isOpen, + attemptsUsed, + answer, + handleHome, + handleContinue, + resultContainerRef, + } = useResult(); + + return ( + +
+
+ logo +

+ {didWin ? 'You Win!' : 'Out of guesses'} +

+ {didWin ? ( +

+ Solved in {attemptsUsed}/{state.maxAttempts} +

+ ) : ( +

Answer

+ )} +
{answer}
+
+
+ + {state.mode === 'endless' && ( + + )} +
+
+
+ ); +}; diff --git a/web/samples/WordWiz/src/components/Root.tsx b/web/samples/WordWiz/src/components/Root.tsx new file mode 100644 index 0000000000..c64a2fee84 --- /dev/null +++ b/web/samples/WordWiz/src/components/Root.tsx @@ -0,0 +1,27 @@ +import { App } from '@app/components/App.tsx'; +import { ErrorBoundary } from '@app/components/ErrorBoundary.tsx'; + +function ErrorBoundaryError({ error }: { error: unknown }) { + return ( +
+

An unhandled error occurred:

+
+ + {error instanceof Error + ? error.message + : typeof error === 'string' + ? error + : JSON.stringify(error)} + +
+
+ ); +} + +export function Root() { + return ( + + + + ); +} diff --git a/web/samples/WordWiz/src/components/hooks/useGrid.ts b/web/samples/WordWiz/src/components/hooks/useGrid.ts new file mode 100644 index 0000000000..73ed7d55b1 --- /dev/null +++ b/web/samples/WordWiz/src/components/hooks/useGrid.ts @@ -0,0 +1,135 @@ +import { MAX_ATTEMPTS, TIPS, WORD_LENGTH } from '@app/game/constants.ts'; +import { useCallback, useEffect, useRef } from 'react'; +import { GameState, LetterMark } from '@app/game/types.ts'; +import { useGameStore } from '@app/context/GameStoreContext.tsx'; + +export const useGrid = () => { + const store = useGameStore(); + const tipTextTimeoutRef = useRef(null); + const tipTextRef = useRef(null); + const gridRowsRef = useRef<(HTMLDivElement | null)[]>( + Array.from({ length: MAX_ATTEMPTS }, (): HTMLDivElement | null => null), + ); + const tilesRef = useRef<(HTMLDivElement | null)[][]>( + Array.from({ length: MAX_ATTEMPTS }, () => + Array.from({ length: WORD_LENGTH }, (): HTMLDivElement | null => null), + ), + ); + + const setTilesRef = useCallback( + (row: number, col: number) => (el: HTMLDivElement) => { + if (!el) return; + tilesRef.current[row][col] = el; + }, + [], + ); + + const setGridRowsRef = useCallback( + (row: number) => (el: HTMLDivElement) => { + if (!el) return; + gridRowsRef.current[row] = el; + }, + [], + ); + + const handleGridChange = useCallback((state: GameState) => { + if (state.status === 'revealing') return; + + const grid = state.grid; + for (let row = 0; row < MAX_ATTEMPTS; row++) { + for (let col = 0; col < WORD_LENGTH; col++) { + const tile = grid[row][col]; + const el = tilesRef.current[row][col]; + if (!el) continue; + + if (!tile) { + el.textContent = ''; + el.dataset['animation'] = 'idle'; + continue; + } + + el.textContent = tile.letter; + el.dataset['animation'] = 'pulse'; + } + } + }, []); + + const handleFlipTile = useCallback( + (row: number, col: number, mark: LetterMark) => { + const el = tilesRef.current[row][col]; + if (!el) return; + + el.dataset['animation'] = 'flip'; + el.addEventListener( + 'animationend', + () => { + el.classList.remove('tile--correct', 'tile--present', 'tile--absent'); + el.classList.add(`tile--${mark}`); + }, + { once: true }, + ); + }, + [], + ); + + const handleShakeGridRow = useCallback((row: number, reason: string) => { + const el = gridRowsRef.current[row]; + if (!el) return; + + if (tipTextRef.current) { + const tipText = tipTextRef.current; + tipText.classList.add('notification'); + tipText.textContent = reason; + + if (tipTextTimeoutRef.current) { + clearTimeout(tipTextTimeoutRef.current); + tipTextTimeoutRef.current = null; + } + + tipTextTimeoutRef.current = setTimeout(() => { + tipText.classList.remove('notification'); + tipText.textContent = TIPS; + }, 2000); + } + + el.dataset['animation'] = 'shake'; + el.addEventListener( + 'animationend', + () => { + el.dataset['animation'] = 'idle'; + }, + { once: true }, + ); + }, []); + + const reset = () => { + for (let row = 0; row < MAX_ATTEMPTS; row++) { + for (let col = 0; col < WORD_LENGTH; col++) { + const el = tilesRef.current[row][col]; + if (!el) continue; + + el.textContent = ''; + el.dataset['animation'] = 'idle'; + el.classList.remove('tile--correct', 'tile--present', 'tile--absent'); + } + } + if (tipTextRef.current) { + tipTextRef.current.textContent = TIPS; + } + if (tipTextTimeoutRef.current) clearTimeout(tipTextTimeoutRef.current); + }; + + useEffect(() => { + store.subscribe(handleGridChange); + store.setCallback({ + onShakeGridRow: handleShakeGridRow, + onFlipTile: handleFlipTile, + }); + window.addEventListener('reset', reset); + return () => { + window.removeEventListener('reset', reset); + }; + }, []); + + return { tipTextRef, setTilesRef, setGridRowsRef }; +}; diff --git a/web/samples/WordWiz/src/components/hooks/useHeader.ts b/web/samples/WordWiz/src/components/hooks/useHeader.ts new file mode 100644 index 0000000000..b3cd560eb5 --- /dev/null +++ b/web/samples/WordWiz/src/components/hooks/useHeader.ts @@ -0,0 +1,21 @@ +import { useGameStore } from '@app/context/GameStoreContext.tsx'; +import { useCallback, useEffect, useState } from 'react'; + +export const useHeader = () => { + const store = useGameStore(); + const state = store.getState(); + const [stats, setStats] = useState(store.stats); + + const handleStatUpdate = useCallback(() => { + setStats(store.stats); + }, []); + + useEffect(() => { + window.addEventListener('stats_updated', handleStatUpdate); + return () => { + window.removeEventListener('stats_updated', handleStatUpdate); + }; + }, []); + + return { state, stats }; +}; diff --git a/web/samples/WordWiz/src/components/hooks/useKeyboard.ts b/web/samples/WordWiz/src/components/hooks/useKeyboard.ts new file mode 100644 index 0000000000..0fd497059c --- /dev/null +++ b/web/samples/WordWiz/src/components/hooks/useKeyboard.ts @@ -0,0 +1,101 @@ +import { + MouseEvent as ReactMouseEvent, + useCallback, + useEffect, + useRef, +} from 'react'; +import { useGameStore } from '@app/context/GameStoreContext.tsx'; +import { GameState, Letter } from '@app/game/types.ts'; + +export const useKeyboard = () => { + const store = useGameStore(); + const keyboardKeysRef = useRef([]); + + const setKeyboardKeysRef = useCallback((el: HTMLButtonElement) => { + keyboardKeysRef.current.push(el); + }, []); + + const handleSubmit = () => { + store.submit(); + }; + + const handleDeleteLetter = () => { + store.deleteLetter(); + }; + + const handleOnScreenKeyboardKeyClick = ( + event: ReactMouseEvent, + ) => { + const key = (event.target as HTMLButtonElement).dataset['key']; + if (!key) return; + + store.inputLetter(key.toUpperCase() as Letter); + }; + + const handlePhysicalKeyboardKeyClick = (event: KeyboardEvent) => { + event.preventDefault(); + const key = event.key; + + if (key === 'Enter') { + handleSubmit(); + } else if (key === 'Backspace') { + handleDeleteLetter(); + } else if (/^[a-zA-Z]$/.test(key)) { + store.inputLetter(key.toUpperCase() as Letter); + } + }; + + const handleKeyboardChange = useCallback((state: GameState) => { + if (state.status === 'revealing') return; + + const keyboardState = state.keyboard; + for (let i = 0; i < keyboardKeysRef.current.length; i++) { + const key = keyboardKeysRef.current[i]; + if (!key) continue; + + const keyLetter = key.dataset['key']; + if (!keyLetter) continue; + + const keyLetterMark = keyboardState[keyLetter.toUpperCase() as Letter]; + if (!keyLetterMark) continue; + + key.classList.remove( + 'keyboard-key--correct', + 'keyboard-key--present', + 'keyboard-key--absent', + ); + key.classList.add(`keyboard-key--${keyLetterMark}`); + } + }, []); + + const reset = () => { + for (let i = 0; i < keyboardKeysRef.current.length; i++) { + const key = keyboardKeysRef.current[i]; + if (!key) return; + + key.classList.remove( + 'keyboard-key--correct', + 'keyboard-key--present', + 'keyboard-key--absent', + ); + } + }; + + useEffect(() => { + store.subscribe(handleKeyboardChange); + window.addEventListener('keydown', handlePhysicalKeyboardKeyClick); + window.addEventListener('reset', reset); + return () => { + window.removeEventListener('keydown', handlePhysicalKeyboardKeyClick); + window.removeEventListener('reset', reset); + }; + }, []); + + return { + setKeyboardKeysRef, + handleOnScreenKeyboardKeyClick, + handleSubmit, + handleDeleteLetter, + handleKeyboardChange, + }; +}; diff --git a/web/samples/WordWiz/src/components/hooks/useResult.ts b/web/samples/WordWiz/src/components/hooks/useResult.ts new file mode 100644 index 0000000000..3c4e9cabe8 --- /dev/null +++ b/web/samples/WordWiz/src/components/hooks/useResult.ts @@ -0,0 +1,89 @@ +import { useGameStore } from '@app/context/GameStoreContext.tsx'; +import { useNavigate } from 'react-router-dom'; +import { useEffect, useRef, useState } from 'react'; +import { GameState } from '@app/game/types.ts'; +import { getEndlessSeed } from '@app/game/engine/seed.ts'; +import { useBeam } from '@app/context/BeamContext.tsx'; +import { DAILY_STREAK, ENDLESS_STREAK } from '@app/game/constants.ts'; +import { getAndComputePlayerStats } from '@app/beam.ts'; + +export const useResult = () => { + const beam = useBeam(); + const store = useGameStore(); + const navigate = useNavigate(); + const resultContainerRef = useRef(null); + const [state, setState] = useState(store.getState()); + + const didWin = state.status === 'round_end_win'; + const didLose = state.status === 'round_end_loss'; + const isOpen = didWin || didLose; + const attemptsUsed = didWin ? state.row + 1 : state.maxAttempts; + const answer = state.currentAnswer.join(''); + + const handleGameEnd = async (gameState: GameState) => { + if (resultContainerRef.current) { + resultContainerRef.current.dataset['animation'] = 'reveal'; + resultContainerRef.current.addEventListener( + 'animationend', + () => { + resultContainerRef.current!.dataset['animation'] = 'idle'; + }, + { once: true }, + ); + } + + const newGameState = { ...gameState }; + setState(newGameState); + + const mode = gameState.mode; + const key = mode === 'daily' ? DAILY_STREAK : ENDLESS_STREAK; + + const currentStreak = Number(store.stats[key] ?? '0'); + if (currentStreak === 0 && gameState.status === 'round_end_loss') return; + + const newStreak = + gameState.status === 'round_end_win' ? String(currentStreak + 1) : '0'; + + await beam.stats.set({ + accessType: 'private', + stats: { + [key]: newStreak, + }, + }); + + await getAndComputePlayerStats(beam, store); + }; + + const handleHome = () => { + store.reset(); + dispatchEvent(new CustomEvent('reset')); + setState(store.getState()); + navigate('/'); + }; + + const handleContinue = () => { + const seed = getEndlessSeed(); + store.nextRound(seed); + dispatchEvent(new CustomEvent('reset')); + setState(store.getState()); + }; + + useEffect(() => { + store.setCallback({ + onGameEnd: (gameState) => { + void handleGameEnd(gameState); + }, + }); + }, []); + + return { + state, + didWin, + isOpen, + attemptsUsed, + answer, + handleHome, + handleContinue, + resultContainerRef, + }; +}; diff --git a/web/samples/WordWiz/src/context/BeamContext.tsx b/web/samples/WordWiz/src/context/BeamContext.tsx new file mode 100644 index 0000000000..14c257003b --- /dev/null +++ b/web/samples/WordWiz/src/context/BeamContext.tsx @@ -0,0 +1,23 @@ +import { createContext, useContext, type ReactNode } from 'react'; +import type { Beam } from 'beamable-sdk'; + +// Holds the globally available Beam instance +export const BeamContext = createContext(null); + +export function BeamProvider({ + beam, + children, +}: { + beam: Beam; + children: ReactNode; +}) { + return {children}; +} + +export function useBeam(): Beam { + const ctx = useContext(BeamContext); + if (!ctx) { + throw new Error('useBeam must be used within a BeamProvider'); + } + return ctx; +} diff --git a/web/samples/WordWiz/src/context/GameStoreContext.tsx b/web/samples/WordWiz/src/context/GameStoreContext.tsx new file mode 100644 index 0000000000..7c2404b595 --- /dev/null +++ b/web/samples/WordWiz/src/context/GameStoreContext.tsx @@ -0,0 +1,26 @@ +import { createContext, useContext, type ReactNode } from 'react'; +import { GameStore } from '@app/game/state/store.ts'; + +export const GameStoreContext = createContext(null); + +export function GameStoreProvider({ + store, + children, +}: { + store: GameStore; + children: ReactNode; +}) { + return ( + + {children} + + ); +} + +export function useGameStore(): GameStore { + const ctx = useContext(GameStoreContext); + if (!ctx) { + throw new Error('useGameStore must be used within a GameStoreProvider'); + } + return ctx; +} diff --git a/web/samples/WordWiz/src/game/constants.ts b/web/samples/WordWiz/src/game/constants.ts new file mode 100644 index 0000000000..2487b31b0c --- /dev/null +++ b/web/samples/WordWiz/src/game/constants.ts @@ -0,0 +1,16 @@ +export const WORD_LENGTH = 5; +export const MAX_ATTEMPTS = 6; +export const TILE_FLIP_DURATION = 250; +export const FLIP_STAGGER = 100; +export const RESULT_REVEAL_DELAY = 500; +export const KEYBOARD_ROW = [ + ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'], + ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'], + ['z', 'x', 'c', 'v', 'b', 'n', 'm'], +]; +export const LETTERS = Array.from({ length: 26 }, (_, i) => + String.fromCharCode(65 + i), +); +export const TIPS = 'Green = Correct • Amber = Present • Dark = Absent'; +export const DAILY_STREAK = 'DAILY_STREAK'; +export const ENDLESS_STREAK = 'ENDLESS_STREAK'; diff --git a/web/samples/WordWiz/src/game/engine/guessEvaluator.ts b/web/samples/WordWiz/src/game/engine/guessEvaluator.ts new file mode 100644 index 0000000000..aba75cf39b --- /dev/null +++ b/web/samples/WordWiz/src/game/engine/guessEvaluator.ts @@ -0,0 +1,56 @@ +import { GuessResult, Letter, LetterMark, Tile } from '@app/game/types.ts'; + +export function guessEvaluator( + answer: Letter[], + guess: Letter[], + wordLength = 5, +): GuessResult { + const tiles: Tile[] = []; + const marks: LetterMark[] = []; + + for (let i = 0; i <= wordLength - 1; i++) { + tiles.push({ letter: guess[i], mark: undefined }); + marks.push('absent'); + } + + const answerCounts = answer.reduce( + (letterMap, currentLetter) => { + if (currentLetter in letterMap) { + letterMap[currentLetter] = letterMap[currentLetter] + 1; + } else { + letterMap[currentLetter] = 1; + } + + return letterMap; + }, + {} as Record, + ); + + // First pass (correct): + for (let i = 0; i <= wordLength - 1; i++) { + const guessLetter = guess[i]; + const answerLetter = answer[i]; + if (guessLetter === answerLetter) { + marks[i] = 'correct'; + tiles[i].mark = marks[i]; + answerCounts[guessLetter] = answerCounts[guessLetter] - 1; + } + } + + // Second pass (present/absent): + for (let i = 0; i <= wordLength - 1; i++) { + if (marks[i] === 'correct') continue; + + const guessLetter = guess[i]; + if (answerCounts[guessLetter] > 0) { + marks[i] = 'present'; + tiles[i].mark = marks[i]; + answerCounts[guessLetter] = answerCounts[guessLetter] - 1; + } else { + marks[i] = 'absent'; + tiles[i].mark = marks[i]; + } + } + + return { tiles, marks, isWin: marks.every((mark) => mark === 'correct') }; +} diff --git a/web/samples/WordWiz/src/game/engine/seed.ts b/web/samples/WordWiz/src/game/engine/seed.ts new file mode 100644 index 0000000000..9086b46baf --- /dev/null +++ b/web/samples/WordWiz/src/game/engine/seed.ts @@ -0,0 +1,6 @@ +import answers from '@app/assets/answers.ts'; + +export function getEndlessSeed() { + // generate a random number between 0 and answersLength + return Math.floor(Math.random() * answers.length); +} diff --git a/web/samples/WordWiz/src/game/engine/tests/guessEvaluator.test.ts b/web/samples/WordWiz/src/game/engine/tests/guessEvaluator.test.ts new file mode 100644 index 0000000000..97c589a487 --- /dev/null +++ b/web/samples/WordWiz/src/game/engine/tests/guessEvaluator.test.ts @@ -0,0 +1,78 @@ +import { expect, test } from 'vitest'; +import { guessEvaluator } from '@app/game/engine/guessEvaluator.ts'; +import { Letter } from '@app/game/types.ts'; + +test('answer=APPLE, guess=ALLEY', () => { + const answer = 'APPLE'.split('').map((ch) => ch as Letter); + const guess = 'ALLEY'.split('').map((ch) => ch as Letter); + const result = guessEvaluator(answer, guess); + + expect(result.marks).toEqual([ + 'correct', + 'present', + 'absent', + 'present', + 'absent', + ]); + expect(result.isWin).toBe(false); +}); + +test('answer=LEVEL, guess=LEMON', () => { + const answer = 'LEVEL'.split('').map((ch) => ch as Letter); + const guess = 'LEMON'.split('').map((ch) => ch as Letter); + const result = guessEvaluator(answer, guess); + + expect(result.marks).toEqual([ + 'correct', + 'correct', + 'absent', + 'absent', + 'absent', + ]); + expect(result.isWin).toBe(false); +}); + +test('answer=ABBEY, guess=BABES', () => { + const answer = 'ABBEY'.split('').map((ch) => ch as Letter); + const guess = 'BABES'.split('').map((ch) => ch as Letter); + const result = guessEvaluator(answer, guess); + + expect(result.marks).toEqual([ + 'present', + 'present', + 'correct', + 'correct', + 'absent', + ]); + expect(result.isWin).toBe(false); +}); + +test('answer=CRANE, guess=CRANE', () => { + const answer = 'CRANE'.split('').map((ch) => ch as Letter); + const guess = 'CRANE'.split('').map((ch) => ch as Letter); + const result = guessEvaluator(answer, guess); + + expect(result.marks).toEqual([ + 'correct', + 'correct', + 'correct', + 'correct', + 'correct', + ]); + expect(result.isWin).toBe(true); +}); + +test('answer=PLATE, guess=BRING', () => { + const answer = 'PLATE'.split('').map((ch) => ch as Letter); + const guess = 'BRING'.split('').map((ch) => ch as Letter); + const result = guessEvaluator(answer, guess); + + expect(result.marks).toEqual([ + 'absent', + 'absent', + 'absent', + 'absent', + 'absent', + ]); + expect(result.isWin).toBe(false); +}); diff --git a/web/samples/WordWiz/src/game/engine/tests/seed.test.ts b/web/samples/WordWiz/src/game/engine/tests/seed.test.ts new file mode 100644 index 0000000000..411dce4b14 --- /dev/null +++ b/web/samples/WordWiz/src/game/engine/tests/seed.test.ts @@ -0,0 +1,13 @@ +import { expect, test, vi } from 'vitest'; +import { getEndlessSeed } from '@app/game/engine/seed.ts'; +import answers from '@app/assets/answers.ts'; + +test('getEndlessSeed()', () => { + vi.doMock('@app/assets/answers.ts', () => { + return { default: new Array(answers.length).fill(0) }; + }); + + const seed = getEndlessSeed(); + expect(seed).toBeGreaterThanOrEqual(0); + expect(seed).toBeLessThan(answers.length); +}); diff --git a/web/samples/WordWiz/src/game/engine/tests/validators.test.ts b/web/samples/WordWiz/src/game/engine/tests/validators.test.ts new file mode 100644 index 0000000000..8af9d44cd5 --- /dev/null +++ b/web/samples/WordWiz/src/game/engine/tests/validators.test.ts @@ -0,0 +1,40 @@ +import { expect, test } from 'vitest'; +import { + canSubmit, + isAllowedWord, + isAlphabetic, + isLengthOk, +} from '@app/game/engine/validators.ts'; +import allowedWords from '@app/assets/allowedWords.ts'; + +test('isLengthOk()', () => { + const correct = 'ALLEY'; + const wrong = 'FOOD'; + + expect(isLengthOk(correct)).toBe(true); + expect(isLengthOk(wrong)).toBe(false); +}); + +test('isAlphabetic()', () => { + const correct = 'ALLEY'; + const wrong = 'FOOD1'; + + expect(isAlphabetic(correct)).toBe(true); + expect(isAlphabetic(wrong)).toBe(false); +}); + +test('isAllowedWord()', () => { + const correct = 'ARGON'; + const wrong = 'FOOD'; + + expect(isAllowedWord(correct, allowedWords)).toBe(true); + expect(isAllowedWord(wrong, allowedWords)).toBe(false); +}); + +test('canSubmit()', () => { + const correct = 'ALLEY'; + const wrong = 'FOOD'; + + expect(canSubmit(correct)).toBe(true); + expect(canSubmit(wrong)).toBe(false); +}); diff --git a/web/samples/WordWiz/src/game/engine/validators.ts b/web/samples/WordWiz/src/game/engine/validators.ts new file mode 100644 index 0000000000..678b55eb99 --- /dev/null +++ b/web/samples/WordWiz/src/game/engine/validators.ts @@ -0,0 +1,21 @@ +import { WORD_LENGTH } from '@app/game/constants.ts'; +import allowedWordsDefault from '@app/assets/allowedWords.ts'; +import answersDefault from '@app/assets/answers.ts'; + +export function isLengthOk(word: string) { + return word.length === WORD_LENGTH; +} + +export function isAlphabetic(word: string) { + return /^[A-Z]+$/.test(word); +} + +// If an explicit list is provided, use it. Otherwise, use the default allowed words loaded from allowed.txt +export function isAllowedWord(word: string, allowedWords?: string[]) { + const list = allowedWords ?? allowedWordsDefault.concat(answersDefault); + return list.includes(word.toLowerCase()); +} + +export function canSubmit(word: string) { + return isLengthOk(word) && isAlphabetic(word); +} diff --git a/web/samples/WordWiz/src/game/state/machine.ts b/web/samples/WordWiz/src/game/state/machine.ts new file mode 100644 index 0000000000..99c79da208 --- /dev/null +++ b/web/samples/WordWiz/src/game/state/machine.ts @@ -0,0 +1,91 @@ +import { GameEvent, GameState, Letter, LetterMark } from '@app/game/types.ts'; +import { LETTERS } from '@app/game/constants.ts'; +import { guessEvaluator } from '@app/game/engine/guessEvaluator.ts'; + +export function stateMachine(state: GameState, event: GameEvent) { + const currentRow = state.row; + const maxAttempts = state.maxAttempts; + const gridRow = [...state.grid[currentRow]]; + + switch (event.type) { + case 'INPUT_LETTER': { + if (state.status === 'typing') { + if (gridRow.length < state.wordLength) { + state.grid[currentRow] = [...gridRow, { letter: event.letter }]; + } + } + return state; + } + case 'DELETE': { + if (state.status === 'typing') { + if (gridRow.length > 0) { + gridRow.pop(); + state.grid[currentRow] = [...gridRow]; + } + } + return state; + } + case 'SUBMIT': { + if (state.status === 'typing') { + state.isSubmitting = true; + state.status = 'validating'; + const lastResult = guessEvaluator( + state.currentAnswer, + gridRow.map((tile) => tile.letter), + ); + state.lastResult = lastResult; + state.grid[currentRow] = state.grid[currentRow].map((tile, i) => ({ + ...tile, + mark: lastResult.marks[i], + })); + state.status = 'revealing'; + } + return state; + } + case 'REVEAL_DONE': { + state.isSubmitting = false; + const lastResult = state.lastResult; + if (!lastResult) return state; + + if (lastResult.isWin) { + state.status = 'round_end_win'; + } else if (currentRow === maxAttempts - 1) { + state.status = 'round_end_loss'; + } else { + state.status = 'typing'; + state.row += 1; + } + return state; + } + case 'NEXT_ROUND': { + state = { + ...state, + row: 0, + grid: Array.from({ length: state.maxAttempts }, () => []), + keyboard: LETTERS.reduce( + (acc, cur) => ({ + ...acc, + [cur]: undefined, + }), + {} as Record, + ), + answerSeed: event.answerSeed, + currentAnswer: event.currentAnswer, + status: 'typing', + lastResult: undefined, + }; + return state; + } + case 'MODE_CHANGE': { + if (state.status === 'idle' || state.status === 'typing') { + state.mode = event.mode; + state.status = 'typing'; + state.answerSeed = event.answerSeed; + state.currentAnswer = event.currentAnswer; + } + return state; + } + default: + return state; + } +} diff --git a/web/samples/WordWiz/src/game/state/store.ts b/web/samples/WordWiz/src/game/state/store.ts new file mode 100644 index 0000000000..8269101808 --- /dev/null +++ b/web/samples/WordWiz/src/game/state/store.ts @@ -0,0 +1,195 @@ +import { + GameEvent, + GameMode, + GameState, + GuessResult, + Letter, + LetterMark, +} from '@app/game/types.ts'; +import { + FLIP_STAGGER, + LETTERS, + RESULT_REVEAL_DELAY, + TILE_FLIP_DURATION, +} from '@app/game/constants.ts'; +import { canSubmit, isAllowedWord } from '@app/game/engine/validators.ts'; +import { stateMachine } from '@app/game/state/machine.ts'; + +interface GameStoreOptions { + mode: GameMode; + answerSeed: number; + wordLength: number; + maxAttempts: number; + getAnswerBySeed(seed: number): Letter[]; + onShakeGridRow?(this: void, row: number, reason: string): void; + onFlipTile?(this: void, row: number, col: number, mark: LetterMark): void; + onRowRevealStart?(this: void, row: number): void; + onRowRevealEnd?(this: void, row: number, result: GuessResult): void; + onGameEnd?(this: void, state: GameState): void; +} + +export interface GameStoreCallbacks { + onShakeGridRow?(this: void, row: number, reason: string): void; + onFlipTile?(this: void, row: number, col: number, mark: LetterMark): void; + onRowRevealStart?(this: void, row: number): void; + onRowRevealEnd?(this: void, row: number, result: GuessResult): void; + onGameEnd?(this: void, state: GameState): void; +} + +export interface GameStore { + stats: Record; + getState(): GameState; + dispatch(event: GameEvent): void; + subscribe(fn: (state: GameState) => void): void; + inputLetter(letter: Letter): void; + deleteLetter(): void; + submit(): void; + nextRound(answerSeed: number): void; + changeMode(mode: GameMode, answerSeed: number): void; + setCallback(callback: GameStoreCallbacks): void; + reset(): void; +} + +function buildInitialState(options: GameStoreOptions): GameState { + return { + mode: options.mode, + row: 0, + grid: Array.from({ length: options.maxAttempts }, () => []), + keyboard: LETTERS.reduce( + (acc, cur) => ({ ...acc, [cur]: undefined }), + {} as Record, + ), + currentAnswer: options.getAnswerBySeed(options.answerSeed), + answerSeed: options.answerSeed, + wordLength: options.wordLength, + maxAttempts: options.maxAttempts, + isSubmitting: false, + status: 'idle', + lastResult: undefined, + }; +} + +export function createGameStore(options: GameStoreOptions): GameStore { + let state = buildInitialState(options); + let subscribers: ((state: GameState) => void)[] = []; + + const store: GameStore = { + stats: {}, + getState() { + return state; + }, + dispatch(event: GameEvent) { + state = stateMachine(state, event); + subscribers.forEach((fn) => fn(state)); + }, + subscribe(fn: (state: GameState) => void) { + subscribers.push(fn); + }, + inputLetter(letter: Letter) { + store.dispatch({ type: 'INPUT_LETTER', letter }); + }, + deleteLetter() { + store.dispatch({ type: 'DELETE' }); + }, + submit() { + if (state.isSubmitting) return; + + const gridRow = state.grid[state.row]; + const rowWord = gridRow.map((tile) => tile.letter).join(''); + + if (!canSubmit(rowWord)) { + options.onShakeGridRow?.(state.row, 'Not enough letters'); + return; + } + + if (!isAllowedWord(rowWord)) { + options.onShakeGridRow?.(state.row, 'Not in word list'); + return; + } + + store.dispatch({ type: 'SUBMIT' }); + + if (state.status !== 'revealing') return; + + // Reveal tiles + const row = state.row; + options.onRowRevealStart?.(row); + for (let col = 0; col <= options.wordLength - 1; col++) { + setTimeout(() => { + const tileLetter = state.grid[row][col].letter; + const keyboardLetterMark = state.keyboard[tileLetter]; + const tileLetterMark = state.grid[row][col].mark; + + options.onFlipTile?.(state.row, col, tileLetterMark ?? 'absent'); + + // Update keyboard state with priority: correct > present > absent + state.keyboard = { + ...state.keyboard, + [state.grid[row][col].letter]: + keyboardLetterMark === 'correct' + ? keyboardLetterMark + : keyboardLetterMark === 'present' && + tileLetterMark === 'absent' + ? keyboardLetterMark + : tileLetterMark, + }; + + if (col !== options.wordLength - 1) return; + + // Last tile, schedule REVEAL_DONE event + setTimeout(() => { + options.onRowRevealEnd?.(row, state.lastResult!); + store.dispatch({ type: 'REVEAL_DONE' }); + + const isNotRoundEnd = + state.status !== 'round_end_win' && + state.status !== 'round_end_loss'; + if (isNotRoundEnd) return; + + setTimeout(() => { + options.onGameEnd?.(state); + }, RESULT_REVEAL_DELAY); + }, TILE_FLIP_DURATION + FLIP_STAGGER); // FLIP_STAGGER as buffer + }, col * FLIP_STAGGER); + } + }, + nextRound(answerSeed: number) { + store.dispatch({ + type: 'NEXT_ROUND', + currentAnswer: options.getAnswerBySeed(answerSeed), + answerSeed, + }); + }, + changeMode(mode: GameMode, answerSeed: number) { + store.dispatch({ + type: 'MODE_CHANGE', + currentAnswer: options.getAnswerBySeed(answerSeed), + mode, + answerSeed, + }); + }, + setCallback(callbacks: GameStoreCallbacks) { + if (callbacks.onShakeGridRow) { + options.onShakeGridRow = callbacks.onShakeGridRow; + } + if (callbacks.onFlipTile) { + options.onFlipTile = callbacks.onFlipTile; + } + if (callbacks.onRowRevealStart) { + options.onRowRevealStart = callbacks.onRowRevealStart; + } + if (callbacks.onRowRevealEnd) { + options.onRowRevealEnd = callbacks.onRowRevealEnd; + } + if (callbacks.onGameEnd) { + options.onGameEnd = callbacks.onGameEnd; + } + }, + reset() { + state = buildInitialState(options); + subscribers = []; + }, + }; + + return store; +} diff --git a/web/samples/WordWiz/src/game/state/tests/machine.test.ts b/web/samples/WordWiz/src/game/state/tests/machine.test.ts new file mode 100644 index 0000000000..215881f91b --- /dev/null +++ b/web/samples/WordWiz/src/game/state/tests/machine.test.ts @@ -0,0 +1,212 @@ +import { expect, test, vi, beforeEach, afterEach } from 'vitest'; +import { createGameStore } from '@app/game/state/store.ts'; +import { LETTERS, MAX_ATTEMPTS, WORD_LENGTH } from '@app/game/constants.ts'; +import { Letter, LetterMark } from '@app/game/types.ts'; +import answers from '@app/assets/answers.ts'; + +beforeEach(() => { + vi.useFakeTimers(); +}); + +afterEach(() => { + vi.useRealTimers(); +}); + +test('Basic typing and guard rails', () => { + const store = createGameStore({ + mode: 'endless', + answerSeed: 0, + wordLength: WORD_LENGTH, + maxAttempts: MAX_ATTEMPTS, + getAnswerBySeed(seed: number): Letter[] { + return answers[seed] + .split('') + .map((letter) => letter.toUpperCase() as Letter); + }, + }); + store.nextRound(0); + + const expected: Letter[] = ['A', 'P', 'P', 'L', 'E']; + const input: Letter[] = ['A', 'P', 'P', 'L', 'E', 'S']; + input.forEach((letter) => store.inputLetter(letter)); + + const state = store.getState(); + const currentRow = state.row; + const gridRow = [...state.grid[currentRow]]; + + expect(gridRow.map((tile) => tile.letter)).toEqual(expected); + + store.deleteLetter(); + store.deleteLetter(); + expect(state.grid[currentRow].length).toBe(3); + + store.submit(); + expect(state.status).not.toBe('validating'); + expect(state.status).toBe('typing'); +}); + +test('Valid submit → reveal → continue typing', () => { + const handleFlipTile = vi.fn(); + const handleRevealStart = vi.fn(); + const handleRevealEnd = vi.fn(); + const store = createGameStore({ + mode: 'endless', + answerSeed: 0, + wordLength: WORD_LENGTH, + maxAttempts: MAX_ATTEMPTS, + getAnswerBySeed(seed: number): Letter[] { + return answers[seed] + .split('') + .map((letter) => letter.toUpperCase() as Letter); + }, + onFlipTile: handleFlipTile, + onRowRevealStart: handleRevealStart, + onRowRevealEnd: handleRevealEnd, + }); + store.nextRound(0); + + const input: Letter[] = ['A', 'B', 'A', 'C', 'K']; + const expectedKeyboard = LETTERS.reduce( + (acc, cur) => ({ ...acc, [cur]: undefined }), + {} as Record, + ); + + input.forEach((letter) => store.inputLetter(letter)); + store.submit(); + + expect(store.getState().status).toBe('revealing'); + expect(handleRevealStart).toBeCalledTimes(1); + expect(handleRevealStart).toBeCalledWith(store.getState().row); + + vi.runAllTimers(); + input.forEach((letter) => (expectedKeyboard[letter] = 'correct')); + + expect(handleFlipTile).toBeCalledTimes(5); + expect(handleRevealEnd).toBeCalledTimes(1); + expect(handleRevealEnd).toBeCalledWith( + store.getState().row, + store.getState().lastResult, + ); + expect(store.getState().status).toBe('round_end_win'); + expect(store.getState().keyboard).toEqual(expectedKeyboard); +}); + +test('Loss after max attempts', () => { + const store = createGameStore({ + mode: 'endless', + answerSeed: 0, + wordLength: WORD_LENGTH, + maxAttempts: MAX_ATTEMPTS, + getAnswerBySeed(seed: number): Letter[] { + return answers[seed] + .split('') + .map((letter) => letter.toUpperCase() as Letter); + }, + }); + store.nextRound(0); + + const input: Letter[] = ['A', 'P', 'P', 'L', 'E']; + for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + input.forEach((letter) => store.inputLetter(letter)); + store.submit(); + vi.runAllTimers(); + } + + expect(store.getState().status).toBe('round_end_loss'); +}); + +test('Next round resets correctly', () => { + const store = createGameStore({ + mode: 'endless', + answerSeed: 0, + wordLength: WORD_LENGTH, + maxAttempts: MAX_ATTEMPTS, + getAnswerBySeed(seed: number): Letter[] { + return answers[seed] + .split('') + .map((letter) => letter.toUpperCase() as Letter); + }, + }); + store.nextRound(0); + + const state = store.getState(); + expect(state.status).toBe('typing'); + expect(state.row).toBe(0); + expect(state.grid.every((row) => row.length === 0)).toBe(true); + expect( + Object.values(state.keyboard).every((mark) => mark === undefined), + ).toBe(true); +}); + +test('Re-entrancy locks', () => { + const store = createGameStore({ + mode: 'endless', + answerSeed: 0, + wordLength: WORD_LENGTH, + maxAttempts: MAX_ATTEMPTS, + getAnswerBySeed(seed: number): Letter[] { + return answers[seed] + .split('') + .map((letter) => letter.toUpperCase() as Letter); + }, + }); + store.nextRound(0); + + const input: Letter[] = ['A', 'P', 'P', 'L', 'E']; + const currentRow = store.getState().row; + input.forEach((letter) => store.inputLetter(letter)); + store.submit(); + + expect(store.getState().status).toBe('revealing'); + + // These should be no-ops: + store.inputLetter('A'); + expect(store.getState().status).toBe('revealing'); + expect(store.getState().grid[currentRow].length).toBe(5); + + store.deleteLetter(); + expect(store.getState().status).toBe('revealing'); + expect(store.getState().grid[currentRow].length).toBe(5); + + store.submit(); + expect(store.getState().status).toBe('revealing'); + expect(store.getState().grid[currentRow].length).toBe(5); + + vi.runAllTimers(); +}); + +test('Mode change while idle/typing', () => { + const store = createGameStore({ + mode: 'endless', + answerSeed: 0, + wordLength: WORD_LENGTH, + maxAttempts: MAX_ATTEMPTS, + getAnswerBySeed(seed: number): Letter[] { + return answers[seed] + .split('') + .map((letter) => letter.toUpperCase() as Letter); + }, + }); + expect(store.getState().mode).toBe('endless'); + + store.dispatch({ + type: 'MODE_CHANGE', + mode: 'daily', + answerSeed: 0, + currentAnswer: ['A', 'P', 'P', 'L', 'E'], + }); + expect(store.getState().mode).toBe('daily'); + expect(store.getState().answerSeed).toBe(0); + + store.nextRound(0); + expect(store.getState().status).toBe('typing'); + + store.dispatch({ + type: 'MODE_CHANGE', + mode: 'endless', + answerSeed: 0, + currentAnswer: ['A', 'P', 'P', 'L', 'E'], + }); + expect(store.getState().mode).toBe('endless'); + expect(store.getState().answerSeed).toBe(0); +}); diff --git a/web/samples/WordWiz/src/game/types.ts b/web/samples/WordWiz/src/game/types.ts new file mode 100644 index 0000000000..db42184b17 --- /dev/null +++ b/web/samples/WordWiz/src/game/types.ts @@ -0,0 +1,69 @@ +export type GameMode = 'daily' | 'endless'; +export type LetterMark = 'correct' | 'present' | 'absent'; +export type Tile = { letter: Letter; mark?: LetterMark }; +export type GuessResult = { + tiles: Tile[]; + marks: LetterMark[]; + isWin: boolean; +}; +export type KeyboardState = Record; +export type GameState = { + mode: GameMode; + row: number; + grid: Tile[][]; + keyboard: KeyboardState; + currentAnswer: Letter[]; + answerSeed: number; + wordLength: number; + maxAttempts: number; + isSubmitting: boolean; + status: GameStateStatus; + lastResult?: GuessResult; +}; +export type GameStateStatus = + | 'idle' + | 'typing' + | 'validating' + | 'revealing' + | 'round_end_win' + | 'round_end_loss' + | 'ready_next'; +export type GameEvent = + | { type: 'INPUT_LETTER'; letter: Letter } + | { type: 'DELETE' } + | { type: 'SUBMIT' } + | { type: 'REVEAL_DONE' } + | { type: 'NEXT_ROUND'; answerSeed: number; currentAnswer: Letter[] } + | { + type: 'MODE_CHANGE'; + mode: GameMode; + answerSeed: number; + currentAnswer: Letter[]; + }; +export type Letter = + | 'A' + | 'B' + | 'C' + | 'D' + | 'E' + | 'F' + | 'G' + | 'H' + | 'I' + | 'J' + | 'K' + | 'L' + | 'M' + | 'N' + | 'O' + | 'P' + | 'Q' + | 'R' + | 'S' + | 'T' + | 'U' + | 'V' + | 'W' + | 'X' + | 'Y' + | 'Z'; diff --git a/web/samples/WordWiz/src/index.css b/web/samples/WordWiz/src/index.css new file mode 100644 index 0000000000..2300023096 --- /dev/null +++ b/web/samples/WordWiz/src/index.css @@ -0,0 +1,626 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap'); + +:root { + /* Colors */ + --color-bg-body: #2b2f36; + --color-surface: #2b2f36; + --color-app-bg: #1c1e22; + --color-landing-bg: #edede9; + --color-text: #ffffff; + --color-text-muted: #6b7280; + --color-footer-text: #6b7280; + + --color-primary: #fff; + --color-primary-hover: #e6e6e6; + + --color-key-bg: #434a54; + --color-key-bg-hover: #5a626e; + --color-key-border: rgba(255, 255, 255, 0.05); + + --color-tile-bg: #434a54; + --color-tile-border: #5a626e; + --color-tile-correct-bg: #3e9f1c; + --color-tile-correct-border: #2f7a14; + --color-tile-present-bg: #c39318; + --color-tile-present-border: #947012; + --color-tile-absent-bg: #3a3a3c; + --color-tile-absent-border: #2a2a2b; + + /* Radii */ + --radius-sm: 4px; + --radius-md: 8px; + + /* Spacing */ + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 20px; + --space-6: 24px; + + /* Typography sizes */ + --text-xs: 12px; + --text-sm: 14px; + --text-md: 16px; + --text-lg: 20px; + --text-xl: 32px; +} + +* { + box-sizing: border-box; + font-family: 'Inter', sans-serif; + font-optical-sizing: auto; + font-weight: 400; +} + +body { + background-color: var(--color-bg-body); + padding: 0; + margin: 0; +} + +p { + margin: 0; +} + +header { + width: 100%; + display: flex; + align-items: center; + padding: var(--space-2); + gap: var(--space-4); + background-color: var(--color-surface); + border: 1px solid var(--color-key-border); + color: var(--color-text); + border-radius: var(--radius-md); +} + +.header-container { + width: 100%; + padding: var(--space-2); +} + +.profile-container { + margin-top: var(--space-6); +} + +.profile-group { + display: flex; + align-items: center; + flex: 1; + gap: 8px; + min-width: 0; +} + +.username { + font-size: var(--text-md); + font-weight: 500; + color: var(--color-text); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex: 1 1 auto; + min-width: 0; + max-width: 100%; +} + +.avatar { + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + border-radius: 50%; + color: var(--color-text); + font-weight: 700; + text-transform: uppercase; + user-select: none; + border: 1px solid var(--color-key-border); + background: var(--color-key-bg); +} + +.btn { + background-color: var(--color-primary); + box-shadow: 0 -4px inset rgba(0, 0, 0, 0.25); + transition: + transform 0.1s ease, + background 0.2s ease, + box-shadow 0.2s ease; + color: var(--color-surface); + padding: var(--space-2) 14px; + display: flex; + align-items: center; + gap: var(--space-2); + border-radius: var(--radius-md); + border: none; + outline: none; + cursor: pointer; + font-weight: 500; + font-size: var(--text-md); + height: 42px; +} + +.btn:hover { + background-color: var(--color-primary-hover); +} + +.btn:active { + transform: translateY(1px); +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; + box-shadow: 0 -4px inset rgba(0, 0, 0, 0.15); +} + +.btn--sm { + box-shadow: 0 -2px inset rgba(0, 0, 0, 0.25); + padding: 6px 12px; + font-size: 14px; + height: 36px; +} + +.tips { + text-align: center; + font-size: var(--text-xs); + font-weight: 500; + color: var(--color-text-muted); + padding: var(--space-1); +} + +.tips.notification { + background-color: var(--color-primary); + border-radius: var(--radius-sm); + color: var(--color-surface); +} + +.grid-container { + display: flex; + flex-direction: column; + align-items: center; +} + +.grid-group { + display: grid; + grid-template-rows: repeat(6, 1fr); + gap: 4px; + padding: 12px; + width: 100%; + height: 100%; + max-width: 280px; + max-height: 332px; +} + +.grid-row { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 4px; +} + +.tile { + background: var(--color-tile-bg); + border: 1px solid var(--color-tile-border); + display: flex; + align-items: center; + justify-content: center; + font-size: var(--text-xl); + font-weight: 700; + color: var(--color-text); + height: 48px; + width: 48px; + border-radius: var(--radius-md); +} + +.tile--correct { + background: var(--color-tile-correct-bg); + border: 1px solid var(--color-tile-correct-border); + color: var(--color-text); +} + +.tile--present { + background: var(--color-tile-present-bg); + border: 1px solid var(--color-tile-present-border); + color: var(--color-text); +} + +.tile--absent { + background: var(--color-tile-absent-bg); + border: 1px solid var(--color-tile-absent-border); + color: var(--color-text); +} + +.keyboard-container { + display: flex; + justify-content: center; + padding: var(--space-2); + width: 100%; +} + +.keyboard-group { + background-color: var(--color-surface); + border: 1px solid var(--color-key-border); + width: 100%; + padding: var(--space-2); + border-radius: var(--radius-md); +} + +.keyboard-row { + display: flex; + justify-content: center; + gap: calc(4px * 1.5); + margin-bottom: var(--space-3); +} + +.keyboard-row:last-child { + margin-bottom: 0; +} + +.keyboard-key { + flex: 1; + background: var(--color-key-bg); + height: 42px; + display: flex; + align-items: center; + justify-content: center; + font-size: var(--text-lg); + font-weight: 700; + color: var(--color-text); + cursor: pointer; + user-select: none; + text-transform: uppercase; + padding: 0; + border: 1px solid var(--color-key-border); + border-radius: var(--radius-md); + transition: + transform 0.1s ease, + background 0.2s ease, + box-shadow 0.2s ease; + box-shadow: + 0 1px 0 rgba(255, 255, 255, 0.05), + inset 0 -2px 0 rgba(0, 0, 0, 0.25); +} + +.keyboard-key:hover { + background-color: var(--color-key-bg-hover); +} + +.keyboard-key:active { + transform: translateY(1px); +} + +.keyboard-key--large { + flex: 1.5; + font-size: var(--text-xs); +} + +.keyboard-key--correct { + background: var(--color-tile-correct-bg); + border: 1px solid var(--color-tile-correct-border); + color: var(--color-text); +} + +.keyboard-key--present { + background: var(--color-tile-present-bg); + border: 1px solid var(--color-tile-present-border); + color: var(--color-text); +} + +.keyboard-key--absent { + background: var(--color-tile-absent-bg); + border: 1px solid var(--color-tile-absent-border); + color: var(--color-text); +} + +.keyboard-edge { + flex: 0.5; +} + +.landing-page { + align-items: center; + justify-items: center; + display: flex; + flex-direction: column; + height: 100vh; + background-color: var(--color-landing-bg); + max-width: 780px; + margin: 0 auto; +} + +.flex-spacer { + flex: 1; +} + +.landing-hero { + align-items: center; + justify-items: center; + display: flex; + flex-direction: column; + gap: var(--space-6); +} + +.logo-img { + width: 250px; + height: auto; +} + +.button-row { + display: flex; + gap: var(--space-3); +} + +.landing-footer { + flex: 1; + text-align: center; + color: var(--color-footer-text); + font-size: var(--text-xs); + margin-bottom: var(--space-6); + display: flex; + flex-direction: column; + justify-content: flex-end; +} + +.text-strong { + font-weight: 700; +} + +.stack-vert { + display: flex; + flex-direction: column; + flex: 0 0 auto; + gap: 2px; +} + +.stack-vert--right { + align-items: flex-end; +} + +.caption { + font-size: var(--text-xs); + text-transform: capitalize; + margin: 0; +} + +.caption--right { + text-align: right; +} + +.data { + font-size: var(--text-sm); + font-weight: 500; + color: var(--color-text); +} + +.data--right { + text-align: right; +} + +.app-page { + align-items: center; + justify-items: center; + display: flex; + flex-direction: column; + height: 100%; + min-height: 100vh; + margin: 0 auto; +} + +.app-page--game { + background-color: var(--color-app-bg); + max-width: 500px; +} + +.game-section { + width: 100%; + align-items: center; + justify-items: center; + display: flex; + flex-direction: column; + gap: var(--space-5); +} + +.tg-sticker { + display: block; + width: 144px; + height: 144px; +} + +.result-dialog { + width: 100%; + height: 100%; + max-width: 500px; + background: rgba(0, 0, 0, 0.4); + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); + border: none; + padding-top: 72px; +} + +.result-container { + background-color: var(--color-surface); + border: 1px solid var(--color-key-border); + width: 100%; + min-height: 350px; + border-radius: var(--radius-md); + display: flex; + flex-direction: column; +} + +.result-container[data-animation='reveal'] { + animation: result_reveal 400ms ease-in; +} + +@keyframes result_reveal { + 0% { + opacity: 0; + transform: translateY(12px) scale(0.98); + } + 60% { + opacity: 1; + transform: translateY(0) scale(1.01); + } + 100% { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.result-header { + padding: var(--space-6) var(--space-3); + text-align: center; + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.result-title { + color: var(--color-text); + font-size: var(--text-xl); + font-weight: 800; + margin-bottom: var(--space-2); +} + +.result-subtitle { + color: var(--color-tile-correct-bg); + font-size: var(--text-sm); + font-weight: 500; +} + +.result-answer { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 6px 12px; + border-radius: var(--radius-sm); + background: var(--color-key-bg); + border: 1px solid var(--color-key-border); + color: var(--color-text); + font-weight: 700; + letter-spacing: 2px; + text-transform: uppercase; + margin-top: var(--space-3); +} + +.result-actions { + display: flex; + justify-content: center; + gap: var(--space-4); + padding: var(--space-4); + border-top: 1px solid var(--color-key-border); +} + +.brand-logo { + width: 100px; + height: auto; + margin-bottom: var(--space-4); +} + +.loading-text { + color: var(--color-text); + font-size: var(--text-md); + font-weight: 500; + text-align: center; +} + +.loading-text::after { + content: '.'; + animation: dots 1s steps(5, start) infinite; +} + +@keyframes dots { + 0%, + 20% { + color: rgba(0, 0, 0, 0); + text-shadow: + 0.25em 0 0 rgba(0, 0, 0, 0), + 0.5em 0 0 rgba(0, 0, 0, 0); + } + 40% { + color: var(--color-text); + text-shadow: + 0.25em 0 0 rgba(0, 0, 0, 0), + 0.5em 0 0 rgba(0, 0, 0, 0); + } + 60% { + text-shadow: + 0.25em 0 0 var(--color-text), + 0.5em 0 0 rgba(0, 0, 0, 0); + } + 80%, + 100% { + text-shadow: + 0.25em 0 0 var(--color-text), + 0.5em 0 0 var(--color-text); + } +} + +.tile[data-animation='pulse'] { + animation-name: tile_pulse; + animation-duration: 100ms; +} + +@keyframes tile_pulse { + 0% { + transform: scale(0.8); + } + + 40% { + transform: scale(1.1); + border-width: 2px; + } +} + +.tile[data-animation='flip'] { + animation-name: tile_flip; + animation-duration: 250ms; + animation-timing-function: ease-in; +} + +@keyframes tile_flip { + 0% { + transform: rotateX(0); + } + + 100% { + transform: rotateX(-90deg); + } +} + +.grid-row[data-animation='shake'] { + animation-name: row_shake; + animation-duration: 600ms; +} + +@keyframes row_shake { + 10%, + 90% { + transform: translateX(-1px); + } + + 20%, + 80% { + transform: translateX(2px); + } + + 30%, + 50%, + 70% { + transform: translateX(-4px); + } + + 40%, + 60% { + transform: translateX(4px); + } +} + +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} diff --git a/web/samples/WordWiz/src/index.tsx b/web/samples/WordWiz/src/index.tsx new file mode 100644 index 0000000000..ae78fb5f21 --- /dev/null +++ b/web/samples/WordWiz/src/index.tsx @@ -0,0 +1,47 @@ +import ReactDOM from 'react-dom/client'; +import { StrictMode } from 'react'; +import { Root } from '@app/components/Root.tsx'; +import { init } from '@app/init.ts'; +import { BeamProvider } from '@app/context/BeamContext.tsx'; +import { createGameStore } from '@app/game/state/store.ts'; +import { GameStoreProvider } from '@app/context/GameStoreContext.tsx'; +import { MAX_ATTEMPTS, WORD_LENGTH } from '@app/game/constants.ts'; +import { Letter } from '@app/game/types.ts'; +import answers from '@app/assets/answers.ts'; +import { getAndComputePlayerStats } from '@app/beam.ts'; +import './index.css'; +import { ErrorBoundary } from './components/ErrorBoundary'; + +const root = ReactDOM.createRoot(document.getElementById('root')!); + +try { + // Configure all application dependencies. + const beam = await init(); + + const store = createGameStore({ + mode: 'endless', + answerSeed: 0, + wordLength: WORD_LENGTH, + maxAttempts: MAX_ATTEMPTS, + getAnswerBySeed(seed: number): Letter[] { + return answers[seed] + .split('') + .map((letter) => letter.toUpperCase() as Letter); + }, + }); + + await getAndComputePlayerStats(beam, store); + + root.render( + + + + + + + , + ); +} catch (error) { + console.error('Failed to initialize WordWiz:', error); + root.render(); +} diff --git a/web/samples/WordWiz/src/init.ts b/web/samples/WordWiz/src/init.ts new file mode 100644 index 0000000000..b22c42538d --- /dev/null +++ b/web/samples/WordWiz/src/init.ts @@ -0,0 +1,9 @@ +import type { Beam } from 'beamable-sdk'; +import { setupBeam } from '@app/beam.ts'; + +/** + * Initializes the application and configures its dependencies. + */ +export async function init(): Promise { + return await setupBeam(); +} diff --git a/web/samples/WordWiz/src/navigation/routes.tsx b/web/samples/WordWiz/src/navigation/routes.tsx new file mode 100644 index 0000000000..4589ec6c0b --- /dev/null +++ b/web/samples/WordWiz/src/navigation/routes.tsx @@ -0,0 +1,16 @@ +import type { ComponentType, JSX } from 'react'; + +import { IndexPage } from '@app/pages/IndexPage.tsx'; +import { GamePage } from '@app/pages/GamePage.tsx'; + +interface Route { + path: string; + Component: ComponentType; + title?: string; + icon?: JSX.Element; +} + +export const routes: Route[] = [ + { path: '/', Component: IndexPage }, + { path: '/game', Component: GamePage }, +]; diff --git a/web/samples/WordWiz/src/pages/GamePage.tsx b/web/samples/WordWiz/src/pages/GamePage.tsx new file mode 100644 index 0000000000..88094f55ec --- /dev/null +++ b/web/samples/WordWiz/src/pages/GamePage.tsx @@ -0,0 +1,21 @@ +import type { FC } from 'react'; +import { Page } from '@app/components/Page.tsx'; +import { Grid } from '@app/components/Grid.tsx'; +import { Keyboard } from '@app/components/Keyboard.tsx'; +import { Header } from '@app/components/Header.tsx'; +import { Result } from '@app/components/Result.tsx'; + +export const GamePage: FC = () => { + return ( + +
+
+
+ + +
+ +
+
+ ); +}; diff --git a/web/samples/WordWiz/src/pages/IndexPage.tsx b/web/samples/WordWiz/src/pages/IndexPage.tsx new file mode 100644 index 0000000000..e4a73eed4d --- /dev/null +++ b/web/samples/WordWiz/src/pages/IndexPage.tsx @@ -0,0 +1,33 @@ +import type { FC } from 'react'; +import { Page } from '@app/components/Page.tsx'; +import logo from '@app/assets/logo.png'; +import EndlessIcon from '@app/assets/EndlessIcon.tsx'; +import { Profile } from '@app/components/Profile.tsx'; +import { useIndexPage } from '@app/pages/hooks/useIndexPage.ts'; + +export const IndexPage: FC = () => { + const { handleEndlessButtonClick } = useIndexPage(); + + return ( + +
+ +
+
+ logo +
+ +
+
+
+

+ powered by Beamable +

+
+
+ + ); +}; diff --git a/web/samples/WordWiz/src/pages/hooks/useIndexPage.ts b/web/samples/WordWiz/src/pages/hooks/useIndexPage.ts new file mode 100644 index 0000000000..df52916881 --- /dev/null +++ b/web/samples/WordWiz/src/pages/hooks/useIndexPage.ts @@ -0,0 +1,16 @@ +import { getEndlessSeed } from '@app/game/engine/seed.ts'; +import { useGameStore } from '@app/context/GameStoreContext.tsx'; +import { useNavigate } from 'react-router-dom'; + +export const useIndexPage = () => { + const store = useGameStore(); + const navigate = useNavigate(); + + const handleEndlessButtonClick = () => { + const seed = getEndlessSeed(); + store.changeMode('endless', seed); + navigate('/game'); + }; + + return { handleEndlessButtonClick }; +}; diff --git a/web/samples/WordWiz/tsconfig.json b/web/samples/WordWiz/tsconfig.json new file mode 100644 index 0000000000..2c3855c6a0 --- /dev/null +++ b/web/samples/WordWiz/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "allowImportingTsExtensions": true, + "isolatedModules": true, + "jsx": "react-jsx", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "paths": { + "@app/*": ["./src/*"], + "beamable-sdk": ["../../src/index.ts"], + "beamable-sdk/*": ["../../src/*"], + "@/defaults": ["../../src/defaults.browser.ts"], + "@/utils/createHash": ["../../src/utils/createHashStub.ts"], + "@/*": ["../../src/*"] + }, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2020", + "useDefineForClassFields": true, + "types": ["vite/client"] + }, + "include": ["src"], + "references": [ + { + "path": "./tsconfig.node.json" + } + ] +} diff --git a/web/samples/WordWiz/tsconfig.node.json b/web/samples/WordWiz/tsconfig.node.json new file mode 100644 index 0000000000..42872c59f5 --- /dev/null +++ b/web/samples/WordWiz/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/web/samples/WordWiz/vite.config.ts b/web/samples/WordWiz/vite.config.ts new file mode 100644 index 0000000000..21d2a34a0c --- /dev/null +++ b/web/samples/WordWiz/vite.config.ts @@ -0,0 +1,56 @@ +import { fileURLToPath } from 'node:url'; +import { dirname, resolve } from 'node:path'; +import { defineConfig } from 'vite'; +import tsconfigPaths from 'vite-tsconfig-paths'; +import react from '@vitejs/plugin-react-swc'; +import mkcert from 'vite-plugin-mkcert'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const projectRoot = resolve(__dirname, '..', '..'); +const sdkSource = resolve(projectRoot, 'src'); +const sampleSource = resolve(__dirname, 'src'); + +// https://vitejs.dev/config/ +export default defineConfig(({ mode }) => ({ + base: mode === 'production' ? './' : '/', + css: { + preprocessorOptions: { + scss: { + api: 'modern', + }, + }, + }, + plugins: [ + // Allows using React dev server along with building a React application with Vite. + // https://npmjs.com/package/@vitejs/plugin-react-swc + react(), + // Allows using the compilerOptions.paths property in tsconfig.json. + // https://www.npmjs.com/package/vite-tsconfig-paths + tsconfigPaths(), + // Creates a custom SSL certificate valid for the local machine. + // Using this plugin requires admin rights on the first dev-mode launch. + // https://www.npmjs.com/package/vite-plugin-mkcert + process.env.HTTPS && mkcert(), + ], + build: { + target: 'esnext', + }, + publicDir: './public', + resolve: { + alias: { + 'beamable-sdk': resolve(sdkSource, 'index.ts'), + '@/defaults': resolve(sdkSource, 'defaults.browser.ts'), + '@/utils/createHash': resolve(sdkSource, 'utils/createHashStub.ts'), + '@': sdkSource, + '@app': sampleSource, + }, + }, + server: { + // Exposes your dev server and makes it accessible for the devices in the same network. + host: true, + fs: { + allow: [projectRoot], + }, + }, +})); diff --git a/web/src/core/types/index.ts b/web/src/core/types/index.ts index 77d851a674..d896598e7e 100644 --- a/web/src/core/types/index.ts +++ b/web/src/core/types/index.ts @@ -1,5 +1,5 @@ export * from './RefreshableServiceMap'; export * from './ServiceMap'; -export { Subscription } from './Subscription'; +export type { Subscription } from './Subscription'; export * from './SubscriptionMap'; export * from './ServerEventType'; diff --git a/web/src/utils/BeamJsonUtils.ts b/web/src/utils/BeamJsonUtils.ts index 994fe25cf7..239e629df5 100644 --- a/web/src/utils/BeamJsonUtils.ts +++ b/web/src/utils/BeamJsonUtils.ts @@ -28,7 +28,7 @@ export class BeamJsonUtils { * Usage: * `JSON.parse(jsonString, BeamJsonUtils.reviver)` */ - static reviver(key: string, value: any): any { + static reviver(_key: string, value: any): any { if (typeof value === 'string') { if (BeamJsonUtils.ISO_DATE_REGEX.test(value)) return new Date(value);