Private
Public Access
Compare commits
58
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e90bbe5cf | ||
|
|
1d8d0ac110 | ||
|
|
007c91f7e9 | ||
|
|
e70cb57176 | ||
|
|
db54899ed4 | ||
|
|
558c8a3bad | ||
|
|
2be49ebdf1 | ||
|
|
8b428260d7 | ||
|
|
306add033a | ||
|
|
f451df283c | ||
|
|
8d542e683e | ||
|
|
dff46cdc6f | ||
|
|
82fff0cdf0 | ||
|
|
4bf7ac358d | ||
|
|
1a97c2c4b4 | ||
|
|
9ea83d7e6e | ||
|
|
f3e4ff211d | ||
|
|
7a86767db1 | ||
|
|
2b1689b321 | ||
|
|
cf56b10aba | ||
|
|
7063704539 | ||
|
|
199bb7e525 | ||
|
|
5daaf71ae7 | ||
|
|
0aeec47996 | ||
|
|
3d67b8f2d9 | ||
|
|
dd9a190fd9 | ||
|
|
f5e5019ea8 | ||
|
|
3f51eb5db6 | ||
|
|
55ef7c9301 | ||
|
|
ddfa395c6b | ||
|
|
8694627817 | ||
|
|
f796819af4 | ||
|
|
b209ad4220 | ||
|
|
df1eefdfe0 | ||
|
|
7aaaf120b2 | ||
|
|
e48d651eb5 | ||
|
|
d704be5bff | ||
|
|
6bf908b43e | ||
|
|
085e010907 | ||
|
|
8935216525 | ||
|
|
1d8efa4e61 | ||
|
|
69fce52bed | ||
|
|
13adf908bf | ||
|
|
3bbfb8740f | ||
|
|
0d04ec91e7 | ||
|
|
20a969705d | ||
|
|
4944d2aa21 | ||
|
|
2ec37a802b | ||
|
|
6a5ba84b5e | ||
|
|
6be0d52fb7 | ||
|
|
f493f94368 | ||
|
|
cd93a26c2c | ||
|
|
175581cdd5 | ||
|
|
5f856e4d70 | ||
|
|
e0495d182e | ||
|
|
0b7c1406cf | ||
|
|
30edc5782b | ||
|
|
d92a7f3aa0 |
@@ -6,6 +6,9 @@
|
||||
APP_ENV=dev
|
||||
APP_SECRET=changethis
|
||||
APP_NAME=mineseeker
|
||||
# Public application release identifier, e.g. 2026.3.0. In Gitea production
|
||||
# deployments this is supplied by the APP_VERSION repository variable.
|
||||
APP_VERSION=1.0.0
|
||||
# APP_PUBLIC_HOSTNAME: The public hostname for your application (used for generating absolute URLs in emails)
|
||||
# For production, set this to your domain (e.g., mineseeker.com)
|
||||
APP_PUBLIC_HOSTNAME=localhost
|
||||
@@ -17,20 +20,22 @@ APP_PUBLIC_HOSTNAME=localhost
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> doctrine/doctrine-bundle ###
|
||||
# Docker PostgreSQL is exposed on port 15432 — use that for bare-metal dev.
|
||||
# Docker PostgreSQL is exposed on port 5452 — use that for bare-metal dev.
|
||||
# Replace POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB with the values from your .env.
|
||||
DATABASE_URL=postgresql://POSTGRES_USER:POSTGRES_PASSWORD@127.0.0.1:15432/POSTGRES_DB?serverVersion=18&charset=utf8
|
||||
DATABASE_URL=postgresql://POSTGRES_USER:POSTGRES_PASSWORD@127.0.0.1:5452/POSTGRES_DB?serverVersion=18&charset=utf8
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
###> google/recaptcha ###
|
||||
RECAPTCHA_SITE_KEY=changethis
|
||||
RECAPTCHA_SECRET_KEY=changethis
|
||||
###< google/recaptcha ###
|
||||
###> trycap.dev/cap ###
|
||||
# CAP_API_ENDPOINT: Full URL including site-key path, e.g. https://cap.example.com/my-site-key/
|
||||
# After deploying the Cap service, create a site in the dashboard and paste the endpoint here.
|
||||
CAP_API_ENDPOINT=http://localhost:3000/changethis/
|
||||
CAP_SECRET_KEY=changethis
|
||||
###< trycap.dev/cap ###
|
||||
|
||||
###> minio/minio ###
|
||||
MINIO_ROOT_USER=changethis
|
||||
MINIO_ROOT_PASSWORD=changethis
|
||||
MINIO_ENDPOINT=http://localhost:9000
|
||||
MINIO_ENDPOINT=http://localhost:9010
|
||||
MINIO_PUBLIC_URL=https://your-minio-subdomain.example.com
|
||||
###< minio/minio ###
|
||||
|
||||
@@ -42,9 +47,9 @@ MAIL_DOMAIN=localhost
|
||||
###> symfony/mercure-bundle ###
|
||||
# See https://symfony.com/doc/current/mercure.html#configuration
|
||||
# The URL of the Mercure hub, used by the app to publish updates (can be a local URL)
|
||||
MERCURE_URL=https://mine.local/.well-known/mercure
|
||||
MERCURE_URL=https://mine.home.arpa/.well-known/mercure
|
||||
# The public URL of the Mercure hub, used by the browser to connect
|
||||
MERCURE_PUBLIC_URL=https://mine.local/.well-known/mercure
|
||||
MERCURE_PUBLIC_URL=https://mine.home.arpa/.well-known/mercure
|
||||
# The secret used to sign the JWTs
|
||||
MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"
|
||||
# Publisher JWT (must match publisher_jwt in your Caddyfile)
|
||||
@@ -54,6 +59,13 @@ MERCURE_SUBSCRIBER_JWT=changethis
|
||||
###< symfony/mercure-bundle ###
|
||||
|
||||
###> web-auth/webauthn-framework ###
|
||||
WEBAUTHN_RP_ID=mine.local
|
||||
WEBAUTHN_ORIGIN=https://mine.local
|
||||
WEBAUTHN_RP_ID=mine.home.arpa
|
||||
WEBAUTHN_ORIGIN=https://mine.home.arpa
|
||||
###< web-auth/webauthn-framework ###
|
||||
|
||||
###> symfony/messenger ###
|
||||
# Choose one of the transports below
|
||||
# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages
|
||||
# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
|
||||
MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
|
||||
###< symfony/messenger ###
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# define your env variables for the test env here
|
||||
KERNEL_CLASS='App\Kernel'
|
||||
APP_SECRET='$ecretf0rt3st'
|
||||
DATABASE_URL="postgresql://system7:bazmeg@127.0.0.1:5452/mineseeker_test?serverVersion=18&charset=utf8"
|
||||
@@ -0,0 +1,142 @@
|
||||
name: CI - Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: splendid-bear
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18-alpine
|
||||
env:
|
||||
POSTGRES_USER: mineseeker_test
|
||||
POSTGRES_PASSWORD: test_password
|
||||
POSTGRES_DB: mineseeker_test
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP 8.3
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.3'
|
||||
extensions: pdo_pgsql, gd, intl, zip, sodium
|
||||
coverage: none
|
||||
tools: composer:v2
|
||||
|
||||
- name: Validate composer.json
|
||||
run: composer validate --strict
|
||||
|
||||
- name: Cache Composer dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: vendor
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-composer-
|
||||
|
||||
- name: Install PHP dependencies
|
||||
run: composer install --prefer-dist --no-progress --no-interaction
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build assets
|
||||
run: npm run build
|
||||
|
||||
- name: Create .env.test file
|
||||
run: |
|
||||
cat > .env.test << 'ENVEOF'
|
||||
APP_ENV=test
|
||||
APP_SECRET=test-secret-key-for-ci-testing-only
|
||||
DATABASE_URL="postgresql://mineseeker_test:test_password@localhost:5432/mineseeker_test?serverVersion=18&charset=utf8"
|
||||
KERNEL_CLASS='App\Kernel'
|
||||
SYMFONY_DEPRECATIONS_HELPER=999999
|
||||
PANTHER_APP_ENV=panther
|
||||
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
|
||||
ENVEOF
|
||||
|
||||
- name: Setup test database
|
||||
run: make test-db-setup
|
||||
|
||||
- name: Run PHPUnit tests
|
||||
run: vendor/bin/phpunit --testdox --colors=always
|
||||
|
||||
- name: Run PHPUnit tests with coverage (optional)
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text --coverage-clover=coverage.xml
|
||||
|
||||
- name: Upload coverage reports (optional)
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
flags: unittests
|
||||
name: phpunit-coverage
|
||||
fail_ci_if_error: false
|
||||
|
||||
lint:
|
||||
runs-on: splendid-bear
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP 8.3
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.3'
|
||||
tools: composer:v2, phpstan
|
||||
|
||||
- name: Install PHP dependencies
|
||||
run: composer install --prefer-dist --no-progress --no-interaction
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run ESLint
|
||||
run: npm run lint || true
|
||||
|
||||
- name: Check code style (PHP)
|
||||
run: |
|
||||
if [ -f "vendor/bin/php-cs-fixer" ]; then
|
||||
vendor/bin/php-cs-fixer fix --dry-run --diff
|
||||
else
|
||||
echo "PHP-CS-Fixer not installed, skipping..."
|
||||
fi
|
||||
@@ -33,7 +33,55 @@ jobs:
|
||||
cd "${{ vars.PROD_APP_DIR }}"
|
||||
docker compose build
|
||||
|
||||
- name: Start services
|
||||
- name: Migrate legacy MinIO and deploy
|
||||
env:
|
||||
APP_VERSION: ${{ vars.APP_VERSION }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd "${{ vars.PROD_APP_DIR }}"
|
||||
docker compose up -d
|
||||
|
||||
MIGRATION_COMPOSE="docker compose --profile storage-migration"
|
||||
LEGACY_MINIO_CONTAINER=$($MIGRATION_COMPOSE ps -a -q minio)
|
||||
|
||||
if [ -n "$LEGACY_MINIO_CONTAINER" ]; then
|
||||
echo "Legacy MinIO found; pausing writers and migrating its buckets to RustFS."
|
||||
APP_WAS_RUNNING=$(docker compose ps --status running -q app)
|
||||
SCHEDULER_WAS_RUNNING=$(docker compose ps --status running -q scheduler)
|
||||
MIGRATION_SUCCEEDED=0
|
||||
|
||||
restart_old_workers_on_failure() {
|
||||
result=$?
|
||||
if [ "$result" -ne 0 ] && [ "$MIGRATION_SUCCEEDED" -eq 0 ]; then
|
||||
# Before a complete copy, restore the old public MinIO endpoint
|
||||
# before bringing back the old app.
|
||||
docker compose stop rustfs || true
|
||||
MINIO_PUBLISHED_API_PORT=9010 MINIO_PUBLISHED_CONSOLE_PORT=9011 $MIGRATION_COMPOSE up -d minio || true
|
||||
[ -z "$APP_WAS_RUNNING" ] || docker compose start app || true
|
||||
[ -z "$SCHEDULER_WAS_RUNNING" ] || docker compose start scheduler || true
|
||||
elif [ "$result" -ne 0 ]; then
|
||||
echo "Data migration succeeded; leaving RustFS in place for recovery from the app rollout failure." >&2
|
||||
fi
|
||||
return "$result"
|
||||
}
|
||||
trap restart_old_workers_on_failure EXIT
|
||||
|
||||
docker compose stop app scheduler || true
|
||||
|
||||
docker compose up -d db mail
|
||||
# Move the legacy service off the RustFS public port before starting RustFS.
|
||||
$MIGRATION_COMPOSE up -d minio
|
||||
docker compose up -d rustfs
|
||||
$MIGRATION_COMPOSE up --build --abort-on-container-exit --exit-code-from storage_migrate storage_migrate
|
||||
MIGRATION_SUCCEEDED=1
|
||||
|
||||
echo "Migration succeeded; deploying the RustFS-only stack."
|
||||
docker compose up -d --wait --wait-timeout 180 app scheduler
|
||||
|
||||
# Remove only the legacy service containers. Do not use -v: the old
|
||||
# MinIO volume is retained as a rollback/backup until manually retired.
|
||||
$MIGRATION_COMPOSE rm -sf minio storage_migrate
|
||||
trap - EXIT
|
||||
else
|
||||
echo "No legacy MinIO container found; deploying the RustFS-only stack."
|
||||
docker compose up -d
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
name: Deploy to Production
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: splendid-bear
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18-alpine
|
||||
env:
|
||||
POSTGRES_USER: mineseeker_test
|
||||
POSTGRES_PASSWORD: test_password
|
||||
POSTGRES_DB: mineseeker_test
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP 8.3
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.3'
|
||||
extensions: pdo_pgsql, gd, intl, zip, sodium
|
||||
coverage: none
|
||||
tools: composer:v2
|
||||
|
||||
- name: Install PHP dependencies
|
||||
run: composer install --prefer-dist --no-progress --no-interaction
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build assets
|
||||
run: npm run build
|
||||
|
||||
- name: Create .env.test file
|
||||
run: |
|
||||
cat > .env.test << 'ENVEOF'
|
||||
APP_ENV=test
|
||||
APP_SECRET=test-secret-key-for-ci-testing-only
|
||||
DATABASE_URL="postgresql://mineseeker_test:test_password@localhost:5432/mineseeker_test?serverVersion=18&charset=utf8"
|
||||
KERNEL_CLASS='App\Kernel'
|
||||
SYMFONY_DEPRECATIONS_HELPER=999999
|
||||
ENVEOF
|
||||
|
||||
- name: Setup test database
|
||||
run: make test-db-setup
|
||||
|
||||
- name: Run PHPUnit tests
|
||||
run: vendor/bin/phpunit --testdox --colors=always --stop-on-failure
|
||||
|
||||
deploy:
|
||||
needs: test
|
||||
runs-on: splendid-bear
|
||||
steps:
|
||||
- name: Checkout tag
|
||||
env:
|
||||
GITEA_TOKEN: ${{ gitea.token }}
|
||||
run: |
|
||||
set -e
|
||||
export HOME=/tmp
|
||||
git config --global credential.helper '!f() { echo "username=oauth2"; echo "password=$GITEA_TOKEN"; }; f'
|
||||
git config --global --add safe.directory "${{ vars.PROD_APP_DIR }}"
|
||||
cd "${{ vars.PROD_APP_DIR }}"
|
||||
git remote set-url origin "${{ gitea.server_url }}/${{ gitea.repository }}.git"
|
||||
git fetch --tags --force
|
||||
git checkout "${{ gitea.ref_name }}"
|
||||
|
||||
- name: Write .env
|
||||
env:
|
||||
PROD_ENV_FILE: ${{ secrets.PROD_ENV_FILE }}
|
||||
run: |
|
||||
printf '%s' "$PROD_ENV_FILE" > "${{ vars.PROD_APP_DIR }}/.env"
|
||||
|
||||
- name: Build image
|
||||
run: |
|
||||
cd "${{ vars.PROD_APP_DIR }}"
|
||||
docker compose build
|
||||
|
||||
- name: Run database migrations
|
||||
run: |
|
||||
cd "${{ vars.PROD_APP_DIR }}"
|
||||
docker compose run --rm app php bin/console doctrine:migrations:migrate --no-interaction
|
||||
|
||||
- name: Clear cache
|
||||
run: |
|
||||
cd "${{ vars.PROD_APP_DIR }}"
|
||||
docker compose run --rm app php bin/console cache:clear --env=prod
|
||||
|
||||
- name: Start services
|
||||
run: |
|
||||
cd "${{ vars.PROD_APP_DIR }}"
|
||||
docker compose up -d
|
||||
|
||||
- name: Health check
|
||||
run: |
|
||||
sleep 5
|
||||
curl -f http://localhost:10080/ || exit 1
|
||||
|
||||
- name: Notify deployment success
|
||||
if: success()
|
||||
run: |
|
||||
echo "✅ Deployment successful for tag ${{ gitea.ref_name }}"
|
||||
|
||||
- name: Notify deployment failure
|
||||
if: failure()
|
||||
run: |
|
||||
echo "❌ Deployment failed for tag ${{ gitea.ref_name }}"
|
||||
exit 1
|
||||
@@ -1,6 +1,7 @@
|
||||
###> system7 - jotunheimr ###
|
||||
compose.override.yaml
|
||||
+bak/
|
||||
/backups/
|
||||
.idea/
|
||||
node_modules/
|
||||
nohup.out
|
||||
@@ -14,3 +15,8 @@ nohup.out
|
||||
/var/
|
||||
/vendor/
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> phpunit/phpunit ###
|
||||
/phpunit.xml
|
||||
/.phpunit.cache/
|
||||
###< phpunit/phpunit ###
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "/phpactor.schema.json",
|
||||
"symfony.enabled": true,
|
||||
"indexer.exclude_patterns": [
|
||||
"/vendor/**/Tests/**/*",
|
||||
"/vendor/**/tests/**/*",
|
||||
"/var/cache/**/*",
|
||||
"/vendor/composer/**/*"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,463 @@
|
||||
# AI Agent Guidelines for MineSeeker
|
||||
|
||||
This document provides guidelines and context for AI coding agents working on the MineSeeker project.
|
||||
|
||||
## Project Overview
|
||||
|
||||
**MineSeeker** is a real-time multiplayer 1v1 minesweeper game built with Symfony (PHP) and React. Players compete to claim mines on a shared 16×16 grid, with the first to reach 26 mines winning.
|
||||
|
||||
### Tech Stack
|
||||
|
||||
- **Backend:** Symfony 7.2 (PHP 8.3)
|
||||
- **Frontend:** React 18, Vite 6
|
||||
- **Database:** PostgreSQL 17 with materialized views
|
||||
- **Storage:** MinIO (S3-compatible)
|
||||
- **Real-time:** Mercure (Server-Sent Events)
|
||||
- **Styling:** SCSS, MUI (Material-UI), Emotion
|
||||
- **Fonts:** @fontsource packages (web), Carlito-Bold.ttf (server-side images)
|
||||
|
||||
### Key Features
|
||||
|
||||
**Core Gameplay:**
|
||||
- **Multiplayer-focused:** 1v1 competitive gameplay where players race to claim more mines than their opponent
|
||||
- **Win condition:** First player to claim 26 out of 51 mines wins
|
||||
- **Real-time updates:** WebSocket-like gameplay using Mercure (Server-Sent Events)
|
||||
- **Game restoration:** Players can resume unfinished games from where they left off
|
||||
- **Bonus points system:** Rewards skilled play (blind hits, chain combos, edge mines, endgame mines, safe cell reveals)
|
||||
|
||||
**User Features:**
|
||||
- **Authentication:** Password + optional TOTP + optional WebAuthn passkeys
|
||||
- **Anonymous play:** Guest players can play without creating an account
|
||||
- **Profile statistics:** Detailed stats including wins, losses, draws, win rate, average score, total mines hit, and bonus points
|
||||
- **Battle history:** View and replay past games move-by-move
|
||||
|
||||
**Sharing & Social:**
|
||||
- **Battle reports:** Shareable public pages for each completed game (`/battle/{uuid}`)
|
||||
- **OG image generation:** Automatic creation of 1200×630 PNG images for social media sharing (using PHP GD)
|
||||
- **Open Graph tags:** Battle share pages include rich preview cards with player names, avatars, scores, and bonus points
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Backend (Symfony)
|
||||
|
||||
```
|
||||
src/
|
||||
├── Controller/ # HTTP endpoints (game, profile, battle sharing)
|
||||
├── Entity/ # Doctrine ORM entities
|
||||
├── Repository/ # Database queries (uses QueryBuilder, not raw SQL)
|
||||
├── Service/ # Business logic (BattleCardGenerator, WebAuthn, Email)
|
||||
├── Dto/ # Data Transfer Objects (immutable, readonly)
|
||||
├── Util/ # Game logic (TopicManager for Mercure)
|
||||
└── Migrations/ # Database schema changes
|
||||
```
|
||||
|
||||
**Important patterns:**
|
||||
- Use Doctrine ORM QueryBuilder (not raw SQL) in repositories
|
||||
- DTOs are `final readonly` classes with constructor property promotion
|
||||
- Services use dependency injection via `config/services.yaml`
|
||||
- Materialized views for performance (auto-refreshed via triggers)
|
||||
|
||||
### Frontend (React)
|
||||
|
||||
```
|
||||
assets/
|
||||
├── js/
|
||||
│ ├── mine-seeker/ # Main game bundle (self-contained)
|
||||
│ │ ├── MineSeeker.jsx # Root component, wraps GameProvider + QueryClientProvider
|
||||
│ │ ├── components/ # Game-specific components
|
||||
│ │ │ ├── GameBoard.jsx # Main game board grid
|
||||
│ │ │ ├── GameTimer.jsx # Game timer display
|
||||
│ │ │ ├── BonusBox.jsx # Bonus points indicator
|
||||
│ │ │ ├── BonusStatsDialog.jsx # Bonus statistics modal
|
||||
│ │ │ ├── CaptchaOverlay.jsx # Captcha challenge overlay
|
||||
│ │ │ ├── ChallengeCountdown.jsx # Challenge timer
|
||||
│ │ │ ├── OnlinePlayersDialog.jsx # Online players list
|
||||
│ │ │ ├── WaitingOverlayContent.jsx # Waiting for opponent
|
||||
│ │ │ ├── grid/ # Grid-related components (cells, mines)
|
||||
│ │ │ ├── profile/ # In-game profile components (PlayerColumn)
|
||||
│ │ │ ├── timer/ # Timer-related components
|
||||
│ │ │ └── user/ # User-related components
|
||||
│ │ ├── contexts/ # React Context API
|
||||
│ │ │ ├── GameContext.jsx # Game state context
|
||||
│ │ │ └── GameProvider.jsx # Context provider with state logic
|
||||
│ │ ├── hooks/ # Custom React hooks
|
||||
│ │ │ ├── useGameDataProvider.js # React Query data provider
|
||||
│ │ │ ├── useGameRefs.jsx # Refs for DOM elements
|
||||
│ │ │ ├── useGameState.jsx # Game state management
|
||||
│ │ │ ├── useServerCommunication.jsx # Mercure SSE connection
|
||||
│ │ │ └── useStepTimer.jsx # Step-by-step timer
|
||||
│ │ └── utils/ # Game-specific utilities
|
||||
│ │ └── constants.jsx # Game constants, colors, defaults
|
||||
│ ├── components/ # Shared UI components
|
||||
│ ├── utils/ # Shared utilities
|
||||
│ ├── profile.jsx # Profile page entry
|
||||
│ ├── passkey.jsx # Passkey management entry
|
||||
├── css/
|
||||
│ └── homepage/ # SCSS partials (imported by style.homepage.scss)
|
||||
└── fonts/
|
||||
└── Carlito-Bold.ttf # TTF font for PHP GD image generation
|
||||
```
|
||||
|
||||
**Important patterns:**
|
||||
- Vite aliases: `@mine-components`, `@mine-contexts`, `@mine-hooks`, `@mine-utils`, `@global-components`, `@global-utils`
|
||||
- React Query only available inside `mine-seeker` bundle
|
||||
- Avoid circular dependencies (e.g., don't import from `@global-components` inside `components/` directory)
|
||||
- PropTypes required on all components
|
||||
|
||||
---
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Adding a New Feature
|
||||
|
||||
1. **Backend:**
|
||||
- Create migration for schema changes
|
||||
- Add/update entities and repositories
|
||||
- Create DTOs for data transfer
|
||||
- Add controller endpoints
|
||||
- Update service configuration if needed
|
||||
|
||||
2. **Frontend:**
|
||||
- Create components in appropriate bundle
|
||||
- Add PropTypes to all components
|
||||
- Use existing hooks and utilities
|
||||
- Follow styled-components pattern for MUI customization
|
||||
|
||||
3. **Documentation:**
|
||||
- Update relevant docs in `docs/` folder
|
||||
- Add examples if introducing new patterns
|
||||
|
||||
### Database Changes
|
||||
|
||||
- Always create migrations: `bin/console make:migration`
|
||||
- Use Doctrine QueryBuilder in repositories (not raw SQL)
|
||||
- For PostgreSQL-specific features (materialized views, triggers), use raw SQL in migrations only
|
||||
- Materialized views should auto-refresh via triggers
|
||||
|
||||
### Styling
|
||||
|
||||
- **Web fonts:** Use `@fontsource` packages (WOFF/WOFF2)
|
||||
- **Server-side images:** Use TTF fonts in `assets/fonts/` (PHP GD requires TTF)
|
||||
- **CSS:** Create SCSS partials in `assets/css/homepage/`, import in main file
|
||||
- **Components:** Use Emotion styled-components or CSS classes
|
||||
|
||||
### File Headers
|
||||
|
||||
All PHP and JS/JSX files should have this header:
|
||||
|
||||
```php
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
```
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Coding Standards
|
||||
|
||||
### PHP
|
||||
|
||||
- **PSR Standards:** Follow PSR-1, PSR-12 coding standards
|
||||
- **Type declarations:** Use strict types (`declare(strict_types=1)`)
|
||||
- **Property promotion:** Use constructor property promotion for DTOs and services
|
||||
- **Readonly:** Use `readonly` for immutable properties
|
||||
- **Final:** Mark DTOs as `final`
|
||||
- **Doctrine:** Use QueryBuilder with `expr()` methods, not string concatenation
|
||||
- **Null safety:** Use null coalescing `??` and null-safe operator `?->`
|
||||
- **Formatting:** 4-space indentation, opening braces on same line for methods/classes
|
||||
|
||||
**Example DTO:**
|
||||
|
||||
```php
|
||||
final readonly class ProfileGameDto implements JsonSerializable
|
||||
{
|
||||
public function __construct(
|
||||
public ?int $id,
|
||||
public string $redName,
|
||||
public string $blueName,
|
||||
public bool $bothRegistered,
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
### JavaScript/React
|
||||
|
||||
- **Components:** Functional components with hooks
|
||||
- **PropTypes:** Required on all components
|
||||
- **Imports:** Use aliases (`@global-components`, `@mine-hooks`, etc.)
|
||||
- **State:** Use `useState`, `useEffect`, `useCallback`, `useMemo` appropriately
|
||||
- **Avoid:** Circular dependencies, especially with barrel exports
|
||||
|
||||
**Example component:**
|
||||
|
||||
```javascript
|
||||
import React, { useState } from 'react';
|
||||
import { string, number } from 'prop-types';
|
||||
|
||||
export const MyComponent = ({ title, count }) => {
|
||||
const [value, setValue] = useState(0);
|
||||
|
||||
return <div>{title}: {count + value}</div>;
|
||||
};
|
||||
|
||||
MyComponent.propTypes = {
|
||||
title: string.isRequired,
|
||||
count: number.isRequired,
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Important Files & Locations
|
||||
|
||||
### Configuration
|
||||
|
||||
- `config/services.yaml` - Service definitions and parameters
|
||||
- `vite.config.js` - Vite build config, aliases
|
||||
- `.env` - Environment variables (not in git)
|
||||
- `composer.json` - PHP dependencies
|
||||
- `package.json` - Node dependencies
|
||||
|
||||
### Key Services
|
||||
|
||||
- `BattleCardGenerator` - Generates 1200×630 PNG OG images using PHP GD
|
||||
- `TopicManager` - Mercure topic management and game logic
|
||||
- `WebAuthnService` - Passkey authentication
|
||||
- `Email services` - Various email senders in `src/Service/Email/`
|
||||
|
||||
### Important Entities
|
||||
|
||||
- `User` - Registered users
|
||||
- `PlayedGame` - Game records with moves, grid, scores
|
||||
- `RecentBattle` - Read-only entity from materialized view
|
||||
- `UserStats` - Read-only entity from materialized view
|
||||
|
||||
### Documentation
|
||||
|
||||
- `docs/game-mechanics/BONUS_POINTS_SYSTEM.md` - Bonus points reference
|
||||
- `docs/FONTS.md` - Font usage and management
|
||||
- `AGENTS.md` - This file
|
||||
- `CHANGELOG.md` - Project changelog
|
||||
|
||||
---
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### ❌ Don't Do
|
||||
|
||||
- **Don't use raw SQL in repositories** (use Doctrine QueryBuilder)
|
||||
- **Don't create circular imports** (e.g., importing `@global-components` from within `components/`)
|
||||
- **Don't use system fonts directly** (bundle TTF in `assets/fonts/`)
|
||||
- **Don't skip PropTypes** on React components
|
||||
- **Don't use `var`** in JavaScript (use `const` or `let`)
|
||||
- **Don't define variables after using them** (hoisting issues with `const`)
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Use Doctrine QueryBuilder** with `expr()` methods
|
||||
- **Import components directly** to avoid circular dependencies
|
||||
- **Bundle fonts in project** for portability
|
||||
- **Add PropTypes** to all components
|
||||
- **Use `const` for immutable values**, `let` for mutable
|
||||
- **Define variables before using them**
|
||||
|
||||
---
|
||||
|
||||
## Keeping Components in Sync
|
||||
|
||||
### Battle Report Display
|
||||
|
||||
The battle report/statistics appear in **two places** that must be kept synchronized:
|
||||
|
||||
#### 1. BattleDialog Component (React)
|
||||
**File:** `assets/js/components/BattleDialog.jsx`
|
||||
- React dialog component shown on profile page
|
||||
- Uses Material-UI and styled-components
|
||||
- Displays game stats, bonus points, winner information
|
||||
|
||||
#### 2. Battle Share Page (Twig)
|
||||
**File:** `templates/Game/battle_share.html.twig`
|
||||
- Public battle share page (accessible via `/battle/{uuid}`)
|
||||
- Server-rendered HTML with SCSS styling
|
||||
- Shows same information as BattleDialog
|
||||
|
||||
### Synchronization Rules
|
||||
|
||||
**When updating BattleDialog.jsx, also update battle_share.html.twig:**
|
||||
|
||||
✅ Adding new stats or fields
|
||||
✅ Changing display logic (winner calculation, formatting)
|
||||
✅ Modifying bonus points display
|
||||
✅ Updating labels or text
|
||||
|
||||
**Keep in sync:**
|
||||
- Game outcome logic (win/loss/draw/abandoned)
|
||||
- Bonus points formatting
|
||||
- Player name display
|
||||
- Score display
|
||||
- Stats and metadata shown
|
||||
|
||||
**Example:** If you add a new stat to BattleDialog showing "fastest mine claim time", you must also add it to battle_share.html.twig so both displays show the same information.
|
||||
|
||||
---
|
||||
|
||||
## Testing & Building
|
||||
|
||||
### Backend
|
||||
|
||||
```bash
|
||||
# Run migrations
|
||||
bin/console doctrine:migrations:migrate
|
||||
|
||||
# Clear cache
|
||||
bin/console cache:clear
|
||||
|
||||
# Refresh materialized views
|
||||
bin/console dbal:run-sql "REFRESH MATERIALIZED VIEW CONCURRENTLY recent_battles"
|
||||
```
|
||||
|
||||
### Frontend
|
||||
|
||||
```bash
|
||||
# Development build with watch
|
||||
npm run dev
|
||||
|
||||
# Production build
|
||||
npm run build
|
||||
|
||||
# After changing fonts
|
||||
rm -rf var/og-cache/*
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Git Workflow
|
||||
|
||||
### Commit Messages
|
||||
|
||||
Follow conventional commits:
|
||||
|
||||
- `feat: add bonus points to battle cards`
|
||||
- `fix: resolve circular dependency in BattleDialog`
|
||||
- `refactor: use Doctrine QueryBuilder in RecentBattleRepository`
|
||||
- `docs: add AGENTS.md for AI coding agents`
|
||||
- `chore: update dependencies`
|
||||
|
||||
### Creating Pull Requests
|
||||
|
||||
When creating PRs, include:
|
||||
|
||||
1. **Summary** - What was changed and why
|
||||
2. **Changes** - List of modified files/components
|
||||
3. **Testing** - How to verify the changes
|
||||
4. **Screenshots** - For UI changes
|
||||
|
||||
---
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Database
|
||||
|
||||
- Use materialized views for expensive queries (profile stats, recent battles)
|
||||
- Auto-refresh materialized views via triggers on source table changes
|
||||
- Index frequently queried columns
|
||||
- Use `COALESCE()` for nullable aggregates
|
||||
|
||||
### Frontend
|
||||
|
||||
- Lazy load large components
|
||||
- Use React Query for data fetching and caching
|
||||
- Avoid unnecessary re-renders (use `useMemo`, `useCallback`)
|
||||
- Bundle code per entry point (profile, passkey, contact, game)
|
||||
|
||||
### Images
|
||||
|
||||
- Battle card images are cached in `var/og-cache/`
|
||||
- Images regenerated when games change (via deterministic UUID)
|
||||
- Use appropriate image sizes (1200×630 for OG images)
|
||||
|
||||
---
|
||||
|
||||
## Security
|
||||
|
||||
### Authentication
|
||||
|
||||
- Password + TOTP (optional) + WebAuthn passkeys (optional)
|
||||
- Backup codes for TOTP recovery
|
||||
- Session-based authentication with `IS_AUTHENTICATED_REMEMBERED`
|
||||
|
||||
### Data Access
|
||||
|
||||
- Controllers check `denyAccessUnlessGranted()`
|
||||
- Materialized views filter by `user_id`
|
||||
- Guest players use separate `Gamer` entity (no `User` account)
|
||||
|
||||
### Input Validation
|
||||
|
||||
- Symfony forms for user input
|
||||
- File upload validation (size, MIME type)
|
||||
- WebAuthn challenge validation
|
||||
|
||||
---
|
||||
|
||||
## Useful Commands
|
||||
|
||||
```bash
|
||||
# Symfony
|
||||
bin/console debug:container ServiceName # Inspect service configuration
|
||||
bin/console debug:router # List all routes
|
||||
bin/console make:migration # Create new migration
|
||||
bin/console doctrine:migrations:list # List migrations
|
||||
|
||||
# Database
|
||||
bin/console dbal:run-sql "SELECT * FROM ..." # Run SQL query
|
||||
|
||||
# Assets
|
||||
npm run build # Build production assets
|
||||
npm run dev # Build with watch mode
|
||||
|
||||
# Git
|
||||
git log --oneline --graph # View commit history
|
||||
git diff origin/main...HEAD # See changes since main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Need Help?
|
||||
|
||||
- **Bonus Points:** See `docs/game-mechanics/BONUS_POINTS_SYSTEM.md`
|
||||
- **Fonts:** See `docs/FONTS.md`
|
||||
- **Symfony Docs:** https://symfony.com/doc/current/
|
||||
- **React Docs:** https://react.dev/
|
||||
- **Doctrine ORM:** https://www.doctrine-project.org/projects/doctrine-orm/en/current/
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
- **2026-04-21:** Initial AGENTS.md created
|
||||
- Document common patterns, pitfalls, and project structure
|
||||
- Include coding standards and examples
|
||||
|
||||
---
|
||||
|
||||
**Happy coding! 🚀**
|
||||
+147
-1
@@ -1,7 +1,153 @@
|
||||
# Changelog
|
||||
|
||||
|
||||
## (unreleased)
|
||||
## v2026.3.1-2 (2026-09-21)
|
||||
|
||||
### Changes
|
||||
|
||||
* Completely disable the contact form #15. [Lang]
|
||||
|
||||
|
||||
## v2026.3.1-0 (2026-08-18)
|
||||
|
||||
### Changes
|
||||
|
||||
* Add backup/restore scripts #15. [Lang]
|
||||
|
||||
* Fix the changing local url names from legacy .local - & change the minio default port #15. [Lang]
|
||||
|
||||
|
||||
## v2026.3.0-0 (2026-07-27)
|
||||
|
||||
### New
|
||||
|
||||
* Hardening the registration process with removing the not activated registrations #14. [Lang]
|
||||
|
||||
### Changes
|
||||
|
||||
* Add current version of the app in the footer #14. [Lang]
|
||||
|
||||
* Upgrade FE to the latest possible version #14. [Lang]
|
||||
|
||||
* All tests passed and migrated to Symfony 8 #14. [Lang]
|
||||
|
||||
* Upgrade the BE deps to the latest possible version #14. [Lang]
|
||||
|
||||
* Remove registration notification #14. [Lang]
|
||||
|
||||
* Add a process that removes the unnecessary minio-init when it does not needed anymore #14. [Lang]
|
||||
|
||||
* Removed the obsolete contact form entry - legacy code from Google ReCapthca #14. [Lang]
|
||||
|
||||
|
||||
## v2026.2.10-0 (2026-06-02)
|
||||
|
||||
### Changes
|
||||
|
||||
* Add clickable badges for Cap app-wide #13. [Lang]
|
||||
|
||||
* Upgrade both backend and frontend to the latest available version #13. [Lang]
|
||||
|
||||
* Replace Google ReCaptcha with Cap instance #13. [Lang]
|
||||
|
||||
* Fix all eslint issues - & add example of the testing env #10. [Lang]
|
||||
|
||||
|
||||
## v2026.2.9-0 (2026-04-23)
|
||||
|
||||
### New
|
||||
|
||||
* Add tracking code for the app #10. [Lang]
|
||||
|
||||
|
||||
## v2026.2.8-3 (2026-04-22)
|
||||
|
||||
### Fix
|
||||
|
||||
* The error message cannot be seen during avatar changing #10. [Lang]
|
||||
|
||||
|
||||
## v2026.2.8-2 (2026-04-21)
|
||||
|
||||
### Changes
|
||||
|
||||
* Increase the 2 MB avatar maximum file size to 10 MB #10. [Lang]
|
||||
|
||||
|
||||
## v2026.2.8-1 (2026-04-21)
|
||||
|
||||
### Changes
|
||||
|
||||
* Upgrade front-end & back-end deps to the latest available version #10. [Lang]
|
||||
|
||||
|
||||
## v2026.2.8-0 (2026-04-21)
|
||||
|
||||
### New
|
||||
|
||||
* Add CI/CD improvements - add new CI workflow - & improve the deployment w/ tests #10. [Lang]
|
||||
|
||||
* Add test cases to back-end w/ real database connection in it #10. [Lang]
|
||||
|
||||
### Changes
|
||||
|
||||
* The original CI/CD workflow is restored - the work with tests is postponed #10. [Lang]
|
||||
|
||||
* Add sound when the game start #10. [Lang]
|
||||
|
||||
* Create AGENTS.md file for future maintenance #9. [Lang]
|
||||
|
||||
* Remove the wrongly implemented font installation in docker - & replace it with static font on BattleCardGenerator (it solves the shareable image problem on bare-metal too) #8. [Lang]
|
||||
|
||||
|
||||
## v2026.2.7-1 (2026-04-21)
|
||||
|
||||
### Changes
|
||||
|
||||
* Fine-tune the recent battle list #8. [Lang]
|
||||
|
||||
|
||||
## v2026.2.7-0 (2026-04-21)
|
||||
|
||||
### Changes
|
||||
|
||||
* Small changes on docs - and improve text on homepage #8. [Lang]
|
||||
|
||||
* Massive refactor on front-end for unification and readiness #8. [Lang]
|
||||
|
||||
* Update all doc blocks on back-end #8. [Lang]
|
||||
|
||||
* Small refactors on back-end #8. [Lang]
|
||||
|
||||
* Add RecentBattle entity that is a Materialized View to speed up the view - and further refactor on ProfileController #8. [Lang]
|
||||
|
||||
* Create the UserStats entity what is a Materialized View to store Profile stats for every user - & massive ProfileController refactor #8. [Lang]
|
||||
|
||||
* Refactor the SecurityController #7. [Lang]
|
||||
|
||||
* Refactor the code - there was unnecessary codes and wrongly formatted or designed code that are related to Repositories #7. [Lang]
|
||||
|
||||
* Upgrade the doctrine related back-end pkgs to the latest available version #7. [Lang]
|
||||
|
||||
* Add filter to the Profile page's recent plays and an infite list too #7. [Lang]
|
||||
|
||||
* Upgrade to the latest doctrine pkg on back-end #7. [Lang]
|
||||
|
||||
### Fix
|
||||
|
||||
* Do not hide the end-game overlay ever #8. [Lang]
|
||||
|
||||
* The username was not recognized properly #7. [Lang]
|
||||
|
||||
|
||||
## v2026.2.6-1 (2026-04-19)
|
||||
|
||||
### Fix
|
||||
|
||||
* The PostgreSQL logo was horrible #7. [Lang]
|
||||
|
||||
|
||||
## v2026.2.6-0 (2026-04-19)
|
||||
|
||||
### Changes
|
||||
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
header X-Forwarded-Host {host}
|
||||
|
||||
mercure {
|
||||
transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db}
|
||||
transport bolt {
|
||||
path /data/mercure.db
|
||||
}
|
||||
protocol_version_compatibility 8
|
||||
publisher_jwt {$MERCURE_JWT_SECRET} HS256
|
||||
subscriber_jwt {$MERCURE_JWT_SECRET} HS256
|
||||
anonymous
|
||||
|
||||
+1
-6
@@ -22,13 +22,8 @@ RUN install-php-extensions \
|
||||
apcu \
|
||||
sodium
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
fonts-dejavu-core \
|
||||
fontconfig \
|
||||
&& fc-cache -f -v \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
||||
RUN printf '[PHP]\nupload_max_filesize=10M\npost_max_size=11M\n' > "$PHP_INI_DIR/conf.d/uploads.ini"
|
||||
RUN printf '[opcache]\nopcache.enable=1\nopcache.memory_consumption=256\nopcache.max_accelerated_files=20000\nopcache.validate_timestamps=0\n' \
|
||||
> "$PHP_INI_DIR/conf.d/opcache.ini"
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
.PHONY: help start start-build stop build down ps logs prune-everything db-reset mercure-jwt cache-clear og-cache-clear
|
||||
.PHONY: help start start-build stop build down ps logs prune-everything db-reset mercure-jwt cache-clear og-cache-clear test-db-setup test-db-reset test
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
help:
|
||||
@echo "Available commands:"
|
||||
@echo " make start - Start services without building (uses existing images)"
|
||||
@echo " make start-build - Start services and build images if needed"
|
||||
@echo " make start - Start services (storage migration runs automatically once)"
|
||||
@echo " make start-build - Build and start services (storage migration runs automatically once)"
|
||||
@echo " make stop - Stop running services"
|
||||
@echo " make build - Build Docker images only"
|
||||
@echo " make down - Stop and remove containers/networks"
|
||||
@@ -14,23 +14,46 @@ help:
|
||||
@echo " make ccp - Clear the production cache"
|
||||
@echo " make cache-clear - Clear all caches (Vite, Symfony, node_modules)"
|
||||
@echo " make og-cache-clear - Clear Open Graph cache only"
|
||||
@echo " make test-db-setup - One-time setup: Create test database and run migrations"
|
||||
@echo " make test-db-reset - Reset test database (drop, create, migrate)"
|
||||
@echo " make test - Run PHPUnit tests"
|
||||
|
||||
start:
|
||||
docker compose up -d
|
||||
docker compose up -d db mail
|
||||
@if [ -n "$$(docker compose --profile storage-migration ps -a -q minio)" ]; then \
|
||||
echo "Legacy MinIO found; migrating its data to RustFS..."; \
|
||||
docker compose --profile storage-migration up -d minio; \
|
||||
docker compose up -d rustfs; \
|
||||
docker compose --profile storage-migration up --abort-on-container-exit --exit-code-from storage_migrate storage_migrate; \
|
||||
else \
|
||||
echo "No legacy MinIO container found; starting RustFS only."; \
|
||||
docker compose up -d rustfs; \
|
||||
fi
|
||||
docker compose up -d app scheduler
|
||||
|
||||
start-build:
|
||||
composer i
|
||||
bun run build
|
||||
docker compose up -d --build
|
||||
docker compose up -d --build db mail
|
||||
@if [ -n "$$(docker compose --profile storage-migration ps -a -q minio)" ]; then \
|
||||
echo "Legacy MinIO found; migrating its data to RustFS..."; \
|
||||
docker compose --profile storage-migration up -d minio; \
|
||||
docker compose up -d rustfs; \
|
||||
docker compose --profile storage-migration up --build --abort-on-container-exit --exit-code-from storage_migrate storage_migrate; \
|
||||
else \
|
||||
echo "No legacy MinIO container found; starting RustFS only."; \
|
||||
docker compose up -d rustfs; \
|
||||
fi
|
||||
docker compose up -d --build app scheduler
|
||||
|
||||
stop:
|
||||
docker compose stop
|
||||
docker compose --profile storage-migration stop
|
||||
|
||||
build:
|
||||
docker compose build
|
||||
docker compose --profile storage-migration build
|
||||
|
||||
down:
|
||||
docker compose down
|
||||
docker compose --profile storage-migration down
|
||||
|
||||
prune-everything:
|
||||
@echo "WARNING: This will remove ALL containers, volumes, networks and images!"
|
||||
@@ -39,7 +62,7 @@ prune-everything:
|
||||
echo "Aborted."; \
|
||||
exit 1; \
|
||||
fi
|
||||
docker compose down -v --rmi all --remove-orphans
|
||||
docker compose --profile storage-migration down -v --rmi all --remove-orphans
|
||||
|
||||
mercure-jwt:
|
||||
@php bin/generate-mercure-jwt.php
|
||||
@@ -80,5 +103,23 @@ og-cache-clear:
|
||||
@echo "✓ OG cache cleared!"
|
||||
@echo " Battle card images will be regenerated on next access"
|
||||
|
||||
test-db-setup:
|
||||
@echo "Setting up test database..."
|
||||
@bin/console dbal:run-sql "SELECT 1 FROM pg_database WHERE datname='mineseeker_test'" 2>/dev/null | grep -q 1 || \
|
||||
(bin/console dbal:run-sql "CREATE DATABASE mineseeker_test" && echo "✓ Database 'mineseeker_test' created")
|
||||
@bin/console doctrine:migrations:migrate --env=test --no-interaction --allow-no-migration 2>&1 | grep -v "WARNING" || true
|
||||
@echo "✓ Test database setup complete!"
|
||||
@echo " Database: mineseeker_test"
|
||||
@echo " Run tests with: make test"
|
||||
|
||||
test-db-reset:
|
||||
@echo "Resetting test database..."
|
||||
@bin/console dbal:run-sql "DROP DATABASE IF EXISTS mineseeker_test" --quiet
|
||||
@bin/console dbal:run-sql "CREATE DATABASE mineseeker_test" --quiet
|
||||
@bin/console doctrine:migrations:migrate --env=test --no-interaction --quiet
|
||||
@echo "✓ Test database reset complete!"
|
||||
@echo " Database: mineseeker_test"
|
||||
@echo " Run tests with: make test"
|
||||
|
||||
test:
|
||||
@php -d memory_limit=512M bin/phpunit --testdox --colors=always
|
||||
|
||||
@@ -14,7 +14,7 @@ Created by [SplendidBear](https://www.splendidbear.org).
|
||||
- **Guest & registered play** — jump in anonymously or create an account for stats and history
|
||||
- **Full authentication stack** — email/password, passkeys (WebAuthn), TOTP 2FA with backup codes
|
||||
- **Player profiles** — win/loss/draw stats, per-month charts, recent battle history with shareable replay links
|
||||
- **Profile pictures** — uploaded to MinIO object storage, thumbnails generated on-the-fly by LiipImagine
|
||||
- **Profile pictures** — uploaded to RustFS object storage, thumbnails generated on-the-fly by LiipImagine
|
||||
- **Battle replay sharing** — share a direct link to any finished game
|
||||
- **Docker-ready** — single `make start-build` brings up the full production-like stack
|
||||
|
||||
@@ -28,7 +28,7 @@ Created by [SplendidBear](https://www.splendidbear.org).
|
||||
| Frontend | React 19, Vite, MUI, SCSS |
|
||||
| Database | PostgreSQL 18 |
|
||||
| Real-time | Mercure (built into FrankenPHP / Caddy) |
|
||||
| File storage | MinIO (S3-compatible) |
|
||||
| File storage | RustFS (S3-compatible; one-time migration from legacy MinIO) |
|
||||
| Image processing | LiipImagine + Flysystem |
|
||||
| Server | FrankenPHP (Caddy + PHP in one binary) |
|
||||
| Auth | Symfony Security, Scheb 2FA, web-auth/webauthn-framework |
|
||||
@@ -44,7 +44,7 @@ Created by [SplendidBear](https://www.splendidbear.org).
|
||||
- PostgreSQL 18
|
||||
- Caddy with FrankenPHP and the Mercure module
|
||||
- MailHog (or any SMTP server on port 1025)
|
||||
- MinIO (listening on port 9000)
|
||||
- RustFS (API on port 9010, console on port 9011); legacy MinIO source on 9012/9013 during migration
|
||||
|
||||
### Docker
|
||||
- Docker Engine 24+
|
||||
@@ -75,10 +75,11 @@ Edit `.env` and fill in every value. Key ones:
|
||||
|---|---|
|
||||
| `APP_SECRET` | Random 32-byte hex: `openssl rand -hex 32` |
|
||||
| `POSTGRES_USER/PASSWORD/DB` | Your PostgreSQL credentials |
|
||||
| `MINIO_ROOT_USER/PASSWORD` | MinIO admin credentials |
|
||||
| `MINIO_ENDPOINT` | `http://localhost:9000` (bare-metal) |
|
||||
| `MINIO_PUBLIC_URL` | Public URL browsers use to reach MinIO |
|
||||
| `RECAPTCHA_SITE_KEY/SECRET_KEY` | Google reCAPTCHA v3 keys for your domain |
|
||||
| `MINIO_ROOT_USER/PASSWORD` | S3 storage credentials (used by RustFS and legacy MinIO during migration) |
|
||||
| `MINIO_ENDPOINT` | `http://localhost:9010` (bare-metal) |
|
||||
| `MINIO_PUBLIC_URL` | Public URL browsers use to reach RustFS (including media and LiipImagine thumbnails) |
|
||||
| `CAP_API_ENDPOINT` | Full URL of your Cap instance including the site-key path (e.g. `https://cap.example.com/my-site-key/`) |
|
||||
| `CAP_SECRET_KEY` | Secret key from your Cap site dashboard (stored in `PROD_ENV_FILE` Gitea secret on prod) |
|
||||
| `MERCURE_JWT_SECRET` | Random secret (generated in step 3) |
|
||||
| `MERCURE_JWT_TOKEN` | Signed publisher JWT (generated in step 3) |
|
||||
| `MERCURE_SUBSCRIBER_JWT` | Signed subscriber JWT (generated in step 3) |
|
||||
@@ -103,7 +104,7 @@ sudo systemctl reload caddy
|
||||
make start-build
|
||||
```
|
||||
|
||||
This installs PHP dependencies, builds the frontend assets with Bun, builds the Docker image, and starts all services (`app`, `db`, `mail`, `minio`, `minio_init`).
|
||||
This installs PHP dependencies, builds the frontend assets with Bun, builds the Docker images, and starts all services. RustFS migration runs automatically once before the app and scheduler start.
|
||||
|
||||
The app is available at `http://localhost:10080` (or the domain set in `APP_PUBLIC_HOSTNAME`).
|
||||
|
||||
@@ -118,16 +119,16 @@ bun run dev # Vite dev server with hot-reload
|
||||
php bin/console doctrine:migrations:migrate --no-interaction
|
||||
```
|
||||
|
||||
Start MinIO and MailHog, then open the URL configured in your Caddy vhost.
|
||||
Start RustFS and MailHog, then open the URL configured in your Caddy vhost.
|
||||
|
||||
### 5. MinIO bucket setup
|
||||
### 5. Object storage setup
|
||||
|
||||
**Docker** — handled automatically on first start by the `minio_init` service. No action needed.
|
||||
**Docker** — `make start` and `make start-build` run the one-shot migration before starting the app. Production releases pause writers, migrate and verify all MinIO buckets, start the RustFS-backed app, then remove the MinIO container while retaining its volume. See [Object storage migration](docs/MINIO_INITIALIZATION.md).
|
||||
|
||||
**Bare-metal** — run once after MinIO is up:
|
||||
|
||||
```bash
|
||||
mc alias set local http://localhost:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD
|
||||
mc alias set local http://localhost:9010 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD
|
||||
mc mb local/mineseeker
|
||||
echo '' | mc pipe local/mineseeker/media/.keep
|
||||
echo '' | mc pipe local/mineseeker/cache/.keep
|
||||
@@ -216,14 +217,14 @@ POSTGRES_VERSION=18
|
||||
|
||||
MINIO_ROOT_USER=mineseeker
|
||||
MINIO_ROOT_PASSWORD="<strong password>"
|
||||
MINIO_ENDPOINT=http://minio:9000
|
||||
MINIO_ENDPOINT=http://rustfs:9000
|
||||
MINIO_PUBLIC_URL=https://aws.mineseeker.hu
|
||||
|
||||
MAILER_DSN=smtp://mail:25?verify_peer=0
|
||||
MAIL_DOMAIN=mineseeker.hu
|
||||
|
||||
RECAPTCHA_SITE_KEY="<your reCAPTCHA v3 site key>"
|
||||
RECAPTCHA_SECRET_KEY="<your reCAPTCHA v3 secret key>"
|
||||
CAP_API_ENDPOINT="https://cap.example.com/your-site-key/"
|
||||
CAP_SECRET_KEY="<secret key from your Cap site dashboard>"
|
||||
|
||||
MERCURE_URL=https://mineseeker.hu/.well-known/mercure
|
||||
MERCURE_PUBLIC_URL=https://mineseeker.hu/.well-known/mercure
|
||||
@@ -269,7 +270,7 @@ git tag v2026.01
|
||||
git push origin v2026.01
|
||||
```
|
||||
|
||||
This writes `.env`, builds the Docker image, starts all services, runs migrations, and initialises the MinIO buckets automatically via `minio_init`.
|
||||
This writes `.env`, builds the Docker images, runs database migrations, and—if the legacy MinIO container exists—pauses app writers, copies and verifies all its buckets in RustFS, then removes the MinIO container after the app is running on RustFS. The MinIO data volume is retained.
|
||||
|
||||
#### 4. Verify
|
||||
|
||||
@@ -287,11 +288,15 @@ git push origin v2026.01
|
||||
|
||||
---
|
||||
|
||||
## Game Documentation
|
||||
## Documentation
|
||||
|
||||
For detailed information about game mechanics, bonus systems, and scoring rules, see the [docs](./docs/) directory:
|
||||
For detailed information about game mechanics, bonus systems, fonts, testing, and other technical details, see the [docs](./docs/) directory:
|
||||
|
||||
- **[AI Agent Guidelines](./AGENTS.md)** — Comprehensive guide for AI coding agents working on this project
|
||||
- **[Bonus Points System](./docs/game-mechanics/BONUS_POINTS_SYSTEM.md)** — Complete reference for all bonus point types, calculation rules, and implementation details
|
||||
- **[Fonts](./docs/FONTS.md)** — TrueType fonts used for server-side image generation
|
||||
- **[Testing Guide](./docs/testing/TESTING.md)** — Complete testing setup with Foundry factories, database isolation, and best practices
|
||||
- **[Factory Documentation](./docs/testing/FACTORIES.md)** — Detailed API reference for all test data factories
|
||||
|
||||
---
|
||||
|
||||
@@ -300,3 +305,76 @@ For detailed information about game mechanics, bonus systems, and scoring rules,
|
||||
LGPL-3.0 — see [LICENSE](LICENSE) for details.
|
||||
|
||||
© 2026 [SplendidBear](https://www.splendidbear.org)
|
||||
|
||||
---
|
||||
|
||||
## Testing & CI/CD
|
||||
|
||||
MineSeeker has a comprehensive test suite with **71 automated tests** and continuous integration/deployment pipelines.
|
||||
|
||||
### Quick Start
|
||||
|
||||
```bash
|
||||
# Setup test database (first time only)
|
||||
make test-db-setup
|
||||
|
||||
# Run all tests
|
||||
make test
|
||||
|
||||
# Run with documentation output
|
||||
vendor/bin/phpunit --testdox
|
||||
```
|
||||
|
||||
### Test Suite
|
||||
|
||||
- **71 tests** with **227 assertions**
|
||||
- **Controller tests** - HTTP endpoints, authentication, routing
|
||||
- **DTO tests** - Data serialization and calculations
|
||||
- **Entity tests** - Domain logic and defaults
|
||||
- **Service tests** - Business logic and external APIs
|
||||
- **Integration tests** - Foundry factories and database isolation
|
||||
|
||||
**Test execution time:** ~6-8 seconds
|
||||
|
||||
### Continuous Integration
|
||||
|
||||
**Automated testing** runs on every push/pull request:
|
||||
|
||||
```yaml
|
||||
# .gitea/workflows/ci.yml-bak
|
||||
✓ PHP 8.3 setup with all extensions
|
||||
✓ PostgreSQL 18 service container
|
||||
✓ Composer and npm dependency installation
|
||||
✓ Asset building with Vite
|
||||
✓ Database migrations
|
||||
✓ Full test suite execution
|
||||
✓ Code linting (ESLint, PHP-CS-Fixer)
|
||||
```
|
||||
|
||||
### Continuous Deployment
|
||||
|
||||
**Automated deployment** on version tags (e.g., `v1.2.3`):
|
||||
|
||||
```yaml
|
||||
# .gitea/workflows/deploy.yml
|
||||
1. Run full test suite (blocks deployment if fails)
|
||||
2. Checkout tagged version
|
||||
3. Build Docker image
|
||||
4. Run database migrations
|
||||
5. Restart services
|
||||
6. Health check verification
|
||||
```
|
||||
|
||||
**Deploy to production:**
|
||||
```bash
|
||||
git tag -a v1.2.3 -m "Release version 1.2.3"
|
||||
git push origin v1.2.3
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
- **[Testing Guide](docs/testing/TESTING.md)** - Comprehensive testing documentation
|
||||
- **[Factory Reference](docs/testing/FACTORIES.md)** - Foundry factory API
|
||||
- **[CI/CD Guide](docs/CI_CD.md)** - Pipeline configuration and workflows
|
||||
|
||||
---
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
@keyframes appear {
|
||||
from { opacity: 0; transform: scale(0.94); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#hero-auth {
|
||||
padding: 20px;
|
||||
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
.auth-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -66,7 +75,7 @@
|
||||
border-radius: 10px;
|
||||
padding: 44px 48px 40px;
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
max-width: 520px;
|
||||
backdrop-filter: blur(4px);
|
||||
box-shadow: 0 8px 48px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
@@ -78,6 +87,65 @@
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.auth-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 6px;
|
||||
|
||||
.auth-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-cap-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(149, 207, 245, 0.35);
|
||||
background:
|
||||
linear-gradient(115deg, transparent 0 38%, rgba(255, 255, 255, 0.18) 48%, transparent 58% 100%),
|
||||
rgba(35, 111, 135, 0.2);
|
||||
background-position: 120% 50%, 0 0;
|
||||
background-size: 260% 100%, auto;
|
||||
color: #95cff5;
|
||||
font: 600 11px 'Rajdhani', sans-serif;
|
||||
letter-spacing: 0.8px;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
transition:
|
||||
background-position 650ms ease,
|
||||
border-color 200ms ease,
|
||||
box-shadow 200ms ease,
|
||||
color 200ms ease,
|
||||
transform 200ms ease;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
background-position: -60% 50%, 0 0;
|
||||
border-color: rgba(149, 207, 245, 0.65);
|
||||
box-shadow: 0 0 18px rgba(35, 111, 135, 0.28);
|
||||
color: #d5f1ff;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active,
|
||||
&:visited {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid rgba(149, 207, 245, 0.75);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-sub {
|
||||
font: 400 14px 'Rajdhani', sans-serif;
|
||||
color: rgba(149, 207, 245, 0.6);
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
// ── Avatar ───────────────────────────────────────────────────────────────────
|
||||
|
||||
.bd-avatar-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bd-avatar-ring-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bd-avatar-ring {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 50%;
|
||||
background: var(--bd-avatar-gradient);
|
||||
border: 2px solid var(--bd-avatar-border);
|
||||
box-shadow: var(--bd-avatar-glow);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font: 800 24px 'Rajdhani', sans-serif;
|
||||
color: var(--bd-avatar-color);
|
||||
letter-spacing: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bd-avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.bd-avatar-bonus {
|
||||
position: absolute;
|
||||
bottom: -6px;
|
||||
right: -6px;
|
||||
background: #ffd700;
|
||||
border-radius: 50%;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 0 12px rgba(255, 215, 0, 0.6), 0 0 0 2px rgba(7, 9, 13, 1);
|
||||
border: 2px solid rgba(0, 0, 0, 0.5);
|
||||
z-index: 10;
|
||||
|
||||
i {
|
||||
color: #000;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.bd-avatar-name {
|
||||
font: 700 15px 'Rajdhani', sans-serif;
|
||||
color: var(--bd-avatar-color);
|
||||
letter-spacing: 1px;
|
||||
max-width: 120px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bd-avatar-side {
|
||||
font: 600 10px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
// ── StatRow ──────────────────────────────────────────────────────────────────
|
||||
|
||||
.bd-stat-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 9px 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
|
||||
&__icon {
|
||||
width: 16px;
|
||||
color: rgba(149, 207, 245, 0.4);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font: 500 13px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
flex: 1;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font: 700 13px 'Rajdhani', sans-serif;
|
||||
color: var(--bd-stat-value-color, rgba(255, 255, 255, 0.75));
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
}
|
||||
|
||||
// ── BonusPoints ──────────────────────────────────────────────────────────────
|
||||
|
||||
.bd-bonus {
|
||||
padding: 16px 20px 0;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||
margin: 16px 0;
|
||||
|
||||
&__grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
&__column {
|
||||
padding: 16px;
|
||||
border-radius: 6px;
|
||||
|
||||
&--red {
|
||||
border: 1px solid rgba(173, 10, 5, 0.2);
|
||||
background: rgba(173, 10, 5, 0.05);
|
||||
}
|
||||
|
||||
&--blue {
|
||||
border: 1px solid rgba(149, 207, 245, 0.2);
|
||||
background: rgba(149, 207, 245, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
&__heading {
|
||||
font: 700 12px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
color: #ffd700;
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
|
||||
i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&__rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
// ── BattleDialog header actions & bonus score row ────────────────────────────
|
||||
|
||||
.bd-header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.bd-bonus-score {
|
||||
margin-bottom: 8px;
|
||||
|
||||
&__red {
|
||||
font: 700 13px 'Rajdhani', sans-serif;
|
||||
color: #f67d52;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
i {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
&__blue {
|
||||
font: 700 13px 'Rajdhani', sans-serif;
|
||||
color: #95cff5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
i {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bd-result-badge {
|
||||
background: var(--bd-result-bg);
|
||||
border: 1px solid var(--bd-result-border);
|
||||
color: var(--bd-result-color);
|
||||
}
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
main div.txt {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
max-width: 900px;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
.hero-cta {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
.feature-block {
|
||||
width: 100%;
|
||||
padding: 80px 40px;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
footer {
|
||||
background: #040608;
|
||||
border-top: 1px solid rgba(35, 111, 135, 0.12);
|
||||
@@ -110,3 +119,26 @@ footer {
|
||||
&:hover { color: #95cff5; }
|
||||
}
|
||||
}
|
||||
|
||||
.footer-version {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
margin: 0 7px;
|
||||
padding: 3px 8px 2px;
|
||||
border: 1px solid rgba(149, 207, 245, 0.24);
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(135deg, rgba(35, 111, 135, 0.22), rgba(149, 207, 245, 0.08));
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 0 12px rgba(35, 111, 135, 0.12);
|
||||
color: rgba(180, 224, 248, 0.9);
|
||||
font: 700 10px 'Rajdhani', sans-serif;
|
||||
letter-spacing: 1px;
|
||||
line-height: 1;
|
||||
text-transform: uppercase;
|
||||
vertical-align: middle;
|
||||
|
||||
i {
|
||||
color: #95cff5;
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
header {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
.hero--compact {
|
||||
min-height: unset;
|
||||
padding: 36px 60px 48px;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
.hero {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
@@ -28,6 +28,21 @@
|
||||
box-shadow: 0 8px 48px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
#profile-avatar-root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.profile-avatar__error {
|
||||
font-size: 11px;
|
||||
color: #e57373;
|
||||
text-align: center;
|
||||
max-width: 120px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.profile-avatar {
|
||||
position: relative;
|
||||
width: 80px;
|
||||
@@ -427,10 +442,88 @@
|
||||
}
|
||||
}
|
||||
|
||||
.profile-games__filter-wrap {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.profile-games__filter-icon {
|
||||
position: absolute;
|
||||
left: 14px;
|
||||
font-size: 12px;
|
||||
color: rgba(149, 207, 245, 0.4);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.profile-games__filter {
|
||||
width: 100%;
|
||||
background: rgba(255, 255, 255, 0.025);
|
||||
border: 1px solid rgba(255, 255, 255, 0.07);
|
||||
border-radius: 6px;
|
||||
padding: 9px 14px 9px 36px;
|
||||
font: 500 13px 'Rajdhani', sans-serif;
|
||||
color: #fff;
|
||||
letter-spacing: 0.5px;
|
||||
transition: border-color 200ms ease, background 200ms ease;
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
background: rgba(255, 255, 255, 0.045);
|
||||
border-color: rgba(35, 111, 135, 0.55);
|
||||
}
|
||||
}
|
||||
|
||||
.profile-games {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
|
||||
&.is-filtering + .profile-games__load-more {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.is-filtering .profile-game--hidden:not(.profile-game--filtered-out) {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.profile-game--filtered-out {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__load-more {
|
||||
align-self: center;
|
||||
margin-top: 14px;
|
||||
background: rgba(35, 111, 135, 0.12);
|
||||
color: rgba(149, 207, 245, 0.75);
|
||||
border: 1px solid rgba(35, 111, 135, 0.3);
|
||||
border-radius: 6px;
|
||||
padding: 9px 20px;
|
||||
font: 600 12px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.5px;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
transition: background 200ms ease, border-color 200ms ease, color 200ms ease;
|
||||
|
||||
i {
|
||||
font-size: 11px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(35, 111, 135, 0.22);
|
||||
border-color: rgba(35, 111, 135, 0.55);
|
||||
color: rgba(149, 207, 245, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-game {
|
||||
@@ -469,6 +562,10 @@
|
||||
border-left-color: rgba(255, 193, 7, 0.4);
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
&--hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-game__badge {
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
* {
|
||||
outline: none;
|
||||
padding: 0;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.hero h1 {
|
||||
font-size: 44px;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
main {
|
||||
background: #07090d;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
.back-from-game {
|
||||
display: inline-block;
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
.game-brand-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.back-from-game {
|
||||
display: inline-block;
|
||||
-ms-transform: scale(1);
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
@@ -22,3 +36,70 @@
|
||||
-webkit-transition: all 250ms cubic-bezier(.17, .67, .83, .67);
|
||||
transition: all 250ms cubic-bezier(.17, .67, .83, .67);
|
||||
}
|
||||
|
||||
.game-cap-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
z-index: 20;
|
||||
padding: 5px 10px;
|
||||
border: 1px solid rgba(255, 216, 92, 0.42);
|
||||
-webkit-border-radius: 999px;
|
||||
border-radius: 999px;
|
||||
background:
|
||||
linear-gradient(115deg, transparent 0 38%, rgba(255, 255, 255, 0.22) 48%, transparent 58% 100%),
|
||||
rgba(14, 16, 18, 0.72);
|
||||
background-position: 120% 50%, 0 0;
|
||||
background-size: 260% 100%, auto;
|
||||
-webkit-box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.32), 0 8px 22px rgba(0, 0, 0, 0.28);
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.32), 0 8px 22px rgba(0, 0, 0, 0.28);
|
||||
color: #ffe58a;
|
||||
font: 700 11px 'Rajdhani', sans-serif;
|
||||
letter-spacing: 0.9px;
|
||||
line-height: 1;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
-ms-transform: translateX(-50%);
|
||||
-webkit-transform: translateX(-50%);
|
||||
transform: translateX(-50%);
|
||||
white-space: nowrap;
|
||||
-webkit-transition:
|
||||
background-position 650ms ease,
|
||||
border-color 200ms ease,
|
||||
box-shadow 200ms ease,
|
||||
color 200ms ease,
|
||||
transform 200ms ease;
|
||||
transition:
|
||||
background-position 650ms ease,
|
||||
border-color 200ms ease,
|
||||
box-shadow 200ms ease,
|
||||
color 200ms ease,
|
||||
transform 200ms ease;
|
||||
}
|
||||
|
||||
.game-cap-badge:hover,
|
||||
.game-cap-badge:focus-visible {
|
||||
background-position: -60% 50%, 0 0;
|
||||
border-color: rgba(255, 229, 138, 0.78);
|
||||
-webkit-box-shadow: 0 0 0 1px rgba(255, 229, 138, 0.18), 0 0 24px rgba(255, 195, 50, 0.28);
|
||||
box-shadow: 0 0 0 1px rgba(255, 229, 138, 0.18), 0 0 24px rgba(255, 195, 50, 0.28);
|
||||
color: #fff4bd;
|
||||
text-decoration: none;
|
||||
-ms-transform: translateX(-50%) translateY(-1px);
|
||||
-webkit-transform: translateX(-50%) translateY(-1px);
|
||||
transform: translateX(-50%) translateY(-1px);
|
||||
}
|
||||
|
||||
.game-cap-badge:focus,
|
||||
.game-cap-badge:active,
|
||||
.game-cap-badge:visited {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.game-cap-badge:focus-visible {
|
||||
outline: 2px solid rgba(255, 229, 138, 0.75);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#mine-wrapper .game-wrapper .users .user-container .user-control {
|
||||
background: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.5) 0%, rgba(125, 185, 232, 0) 100%);
|
||||
background: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.5) 0%, rgba(125, 185, 232, 0) 100%);
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#mine-wrapper .grid {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#mine-wrapper .game-wrapper .users .active-mines-container {
|
||||
background: -moz-radial-gradient(center, ellipse cover, rgba(255, 252, 252, 1) 0%, rgba(255, 252, 252, 0.99) 1%, rgba(106, 106, 106, 0.39) 61%, rgba(106, 106, 106, 0) 100%);
|
||||
background: -webkit-radial-gradient(center, ellipse cover, rgba(255, 252, 252, 1) 0%, rgba(255, 252, 252, 0.99) 1%, rgba(106, 106, 106, 0.39) 61%, rgba(106, 106, 106, 0) 100%);
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
display: flex;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
#mine-wrapper .game-wrapper .users {
|
||||
visibility: hidden;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#mine-wrapper .game-timer-container {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#mine-wrapper .game-wrapper .users {
|
||||
width: 180px;
|
||||
padding: 0 10px 0 0;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
.opd-paper {
|
||||
background: #07090d !important;
|
||||
background-image: linear-gradient(rgba(35, 111, 135, 0.08) 1px, transparent 1px),
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
@use "sass:color";
|
||||
|
||||
.twofa-status {
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2019 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
@use 'homepage/reset';
|
||||
@use 'homepage/animations';
|
||||
@use 'homepage/header';
|
||||
@@ -12,4 +21,5 @@
|
||||
@use 'homepage/tech';
|
||||
@use 'homepage/footer';
|
||||
@use 'homepage/profile';
|
||||
@use 'homepage/battle-dialog';
|
||||
@use 'homepage/responsive';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
* Copyright (c) 2019 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
* Copyright (c) 2019 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*!*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2019 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
.mine-beta {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'cap-widget';
|
||||
|
||||
const FORMS_SELECTOR = '.auth-form';
|
||||
const SUBMIT_SELECTOR = 'button[type="submit"], input[type="submit"]';
|
||||
|
||||
const createInvisibleCap = apiEndpoint => {
|
||||
const host = document.createElement('cap-widget');
|
||||
host.style.display = 'none';
|
||||
|
||||
const cap = new window.Cap({ apiEndpoint }, host);
|
||||
|
||||
return { cap, host };
|
||||
};
|
||||
|
||||
const lockSubmit = (form, locked) => {
|
||||
const submit = form.querySelector(SUBMIT_SELECTOR);
|
||||
if (!submit) return;
|
||||
submit.disabled = locked;
|
||||
};
|
||||
|
||||
const bindInvisibleCap = (form, apiEndpoint) => {
|
||||
const { cap, host } = createInvisibleCap(apiEndpoint);
|
||||
form.appendChild(host);
|
||||
|
||||
let solving = null;
|
||||
|
||||
const solveToken = async () => {
|
||||
if (!solving) {
|
||||
solving = cap.solve()
|
||||
.finally(() => {
|
||||
solving = null;
|
||||
});
|
||||
}
|
||||
|
||||
const result = await solving;
|
||||
return result?.token ?? cap.token ?? '';
|
||||
};
|
||||
|
||||
lockSubmit(form, true);
|
||||
|
||||
solveToken()
|
||||
.catch(() => '')
|
||||
.finally(() => lockSubmit(form, false));
|
||||
|
||||
form.addEventListener('submit', async e => {
|
||||
if (cap.token) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
lockSubmit(form, true);
|
||||
|
||||
try {
|
||||
const token = await solveToken();
|
||||
if (!token) {
|
||||
lockSubmit(form, false);
|
||||
return;
|
||||
}
|
||||
form.requestSubmit();
|
||||
} catch (_) {
|
||||
lockSubmit(form, false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const initInvisibleCap = () => {
|
||||
const endpointSource = document.querySelector('[data-cap-api-endpoint]')
|
||||
|| document.getElementById('mine-wrapper');
|
||||
const apiEndpoint = endpointSource?.dataset.capApiEndpoint;
|
||||
|
||||
if ('function' !== typeof window.Cap || !apiEndpoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.querySelectorAll(FORMS_SELECTOR).forEach(form => bindInvisibleCap(form, apiEndpoint));
|
||||
};
|
||||
|
||||
initInvisibleCap();
|
||||
@@ -1,36 +1,34 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
export default function AvatarUpload({ uploadUrl, initialThumbUrl, initials }) {
|
||||
const [thumbUrl, setThumbUrl] = useState(initialThumbUrl || null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
import React, { Fragment, useMemo, useRef } from 'react';
|
||||
import { string } from 'prop-types';
|
||||
import { useProfileDataProvider } from '@mine-hooks/useGameDataProvider';
|
||||
|
||||
export const AvatarUpload = ({ uploadUrl, initialThumbUrl, initials }) => {
|
||||
const inputRef = useRef(null);
|
||||
const [thumbUrl, setThumbUrl] = React.useState(initialThumbUrl || null);
|
||||
const { uploadAvatarMutation: { isPending, error, mutate } } = useProfileDataProvider();
|
||||
|
||||
function handleClick() {
|
||||
inputRef.current?.click();
|
||||
}
|
||||
const errorMessage = useMemo(() => error?.message ?? null, [error]);
|
||||
|
||||
function handleChange(e) {
|
||||
const handleChange = e => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('avatar', file);
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
fetch(uploadUrl, { method: 'POST', body: fd })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
setError(data.error);
|
||||
return;
|
||||
}
|
||||
mutate({ uploadUrl, file }, {
|
||||
onSuccess: data => {
|
||||
setThumbUrl(data.thumbUrl);
|
||||
|
||||
const navImg = document.querySelector('.hero-auth-avatar:not(.hero-auth-avatar--initials)');
|
||||
const navInitials = document.querySelector('.hero-auth-avatar.hero-auth-avatar--initials');
|
||||
|
||||
if (navImg) {
|
||||
navImg.src = data.thumbUrl;
|
||||
} else if (navInitials) {
|
||||
@@ -40,32 +38,39 @@ export default function AvatarUpload({ uploadUrl, initialThumbUrl, initials }) {
|
||||
img.className = 'hero-auth-avatar';
|
||||
navInitials.replaceWith(img);
|
||||
}
|
||||
})
|
||||
.catch(() => setError('Upload failed. Please try again.'))
|
||||
.finally(() => setLoading(false));
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`profile-avatar${loading ? ' profile-avatar--loading' : ''}`}
|
||||
title="Click to change profile picture"
|
||||
onClick={handleClick}
|
||||
>
|
||||
{thumbUrl
|
||||
? <img src={thumbUrl} alt={initials} className="profile-avatar__img" />
|
||||
: <span className="profile-avatar__initials">{initials}</span>
|
||||
}
|
||||
<div className="profile-avatar__overlay">
|
||||
<i className="fa fa-camera" />
|
||||
<Fragment>
|
||||
<div
|
||||
className={`profile-avatar${isPending ? ' profile-avatar--loading' : ''}`}
|
||||
title="Click to change profile picture"
|
||||
onClick={() => inputRef.current?.click()}
|
||||
>
|
||||
{thumbUrl
|
||||
? <img src={thumbUrl} alt={initials} className="profile-avatar__img" />
|
||||
: <span className="profile-avatar__initials">{initials}</span>
|
||||
}
|
||||
<div className="profile-avatar__overlay">
|
||||
<i className="fa fa-camera" />
|
||||
</div>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="file"
|
||||
accept="image/jpeg,image/png,image/gif,image/webp"
|
||||
style={{ display: 'none' }}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="file"
|
||||
accept="image/jpeg,image/png,image/gif,image/webp"
|
||||
style={{ display: 'none' }}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{error && <div className="profile-avatar__error">{error}</div>}
|
||||
</div>
|
||||
{errorMessage && <div className="profile-avatar__error">{errorMessage}</div>}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
AvatarUpload.propTypes = {
|
||||
uploadUrl: string.isRequired,
|
||||
initialThumbUrl: string,
|
||||
initials: string.isRequired,
|
||||
};
|
||||
|
||||
@@ -1,34 +1,23 @@
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { array } from 'prop-types';
|
||||
import { formatDuration } from '@global-utils/format';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||||
import Avatar from './battle-dialog/Avatar';
|
||||
import StatRow from './battle-dialog/StatRow';
|
||||
import BonusPoints from './battle-dialog/BonusPoints';
|
||||
import { createTheme, styled, ThemeProvider } from '@mui/material/styles';
|
||||
import { Avatar } from './battle-dialog/Avatar';
|
||||
import { BonusPoints } from './battle-dialog/BonusPoints';
|
||||
import { StatRow } from './battle-dialog/StatRow';
|
||||
|
||||
const darkTheme = createTheme({ palette: { mode: 'dark' } });
|
||||
|
||||
const DIALOG_SX = {
|
||||
'& .MuiDialog-paper': {
|
||||
background: '#07090d',
|
||||
backgroundImage: `
|
||||
linear-gradient(rgba(35, 111, 135, 0.08) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(35, 111, 135, 0.08) 1px, transparent 1px)
|
||||
`,
|
||||
backgroundSize: '46px 46px',
|
||||
border: '1px solid rgba(35, 111, 135, 0.4)',
|
||||
borderRadius: '12px',
|
||||
boxShadow: '0 0 80px rgba(35, 111, 135, 0.15), 0 32px 80px rgba(0,0,0,0.9)',
|
||||
width: '580px',
|
||||
maxWidth: '94vw',
|
||||
overflow: 'hidden',
|
||||
color: '#fff',
|
||||
},
|
||||
'& .MuiBackdrop-root': {
|
||||
background: 'rgba(2, 4, 8, 0.88)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
},
|
||||
};
|
||||
|
||||
const RESULT_META = {
|
||||
win: {
|
||||
label: 'Victory',
|
||||
@@ -53,7 +42,7 @@ const RESULT_META = {
|
||||
},
|
||||
};
|
||||
|
||||
export default function BattleDialog({ games }) {
|
||||
export const BattleDialog = ({ games }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [game, setGame] = useState(null);
|
||||
const [copied, setCopied] = useState(false);
|
||||
@@ -73,7 +62,7 @@ export default function BattleDialog({ games }) {
|
||||
}, [games]);
|
||||
|
||||
if (!game) {
|
||||
return <ThemeProvider theme={darkTheme}><Dialog open={false} sx={DIALOG_SX} /></ThemeProvider>;
|
||||
return <ThemeProvider theme={darkTheme}><StyledDialog open={false} /></ThemeProvider>;
|
||||
}
|
||||
|
||||
const meta = RESULT_META[game.result] ?? RESULT_META.draw;
|
||||
@@ -82,22 +71,12 @@ export default function BattleDialog({ games }) {
|
||||
const endReason = resign
|
||||
? `${resign.charAt(0).toUpperCase() + resign.slice(1)} resigned`
|
||||
: 26 <= maxPoints ? 'Points' : 'Abandoned';
|
||||
const bothRegistered = game.bothRegistered;
|
||||
const shareUrl = `${window.location.origin}/battle/${game.uuid}`;
|
||||
const canContinue = !resign && 26 > maxPoints;
|
||||
const canContinue = bothRegistered && !resign && 26 > maxPoints;
|
||||
const canShare = !canContinue;
|
||||
const playUrl = `${window.location.origin}/play/${game.uuid}`;
|
||||
|
||||
const formatDuration = (from, to) => {
|
||||
if (!from || !to) return null;
|
||||
const diffMs = new Date(to.replace(' ', 'T')) - new Date(from.replace(' ', 'T'));
|
||||
if (isNaN(diffMs) || 0 >= diffMs) return null;
|
||||
const totalSec = Math.floor(diffMs / 1000);
|
||||
const h = Math.floor(totalSec / 3600);
|
||||
const m = Math.floor((totalSec % 3600) / 60);
|
||||
const s = totalSec % 60;
|
||||
if (0 < h) return `${h}h ${m}m ${s}s`;
|
||||
if (0 < m) return `${m}m ${s}s`;
|
||||
return `${s}s`;
|
||||
};
|
||||
const duration = formatDuration(game.created, game.date);
|
||||
const pointDiff = Math.abs((game.redPoints ?? 0) - (game.bluePoints ?? 0));
|
||||
const winnerColor = (game.redPoints ?? 0) > (game.bluePoints ?? 0) ? '#f67d52'
|
||||
@@ -113,7 +92,7 @@ export default function BattleDialog({ games }) {
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<Dialog open={open} onClose={() => setOpen(false)} sx={DIALOG_SX}>
|
||||
<StyledDialog open={open} onClose={() => setOpen(false)}>
|
||||
<div className="bd">
|
||||
<div className="bd-header">
|
||||
<div className="bd-header-left">
|
||||
@@ -122,7 +101,7 @@ export default function BattleDialog({ games }) {
|
||||
<i className="fa fa-crosshairs" /> Match Details
|
||||
</h2>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<div className="bd-header-actions">
|
||||
{canContinue ? (
|
||||
<a
|
||||
className="bd-continue"
|
||||
@@ -133,7 +112,7 @@ export default function BattleDialog({ games }) {
|
||||
<i className="fa fa-play" />
|
||||
Continue
|
||||
</a>
|
||||
) : (
|
||||
) : canShare ? (
|
||||
<button
|
||||
className={`bd-share${copied ? ' bd-share--copied' : ''}`}
|
||||
onClick={handleShare}
|
||||
@@ -143,7 +122,7 @@ export default function BattleDialog({ games }) {
|
||||
<i className={`fa ${copied ? 'fa-check' : 'fa-share-alt'}`} />
|
||||
{copied ? 'Copied!' : 'Share'}
|
||||
</button>
|
||||
)}
|
||||
) : null}
|
||||
<button className="bd-close" onClick={() => setOpen(false)} aria-label="Close">
|
||||
<i className="fa fa-times" />
|
||||
</button>
|
||||
@@ -160,33 +139,19 @@ export default function BattleDialog({ games }) {
|
||||
<span className="bd-vs-score__sep">:</span>
|
||||
<span className="bd-vs-score__blue">{game.bluePoints ?? '—'}</span>
|
||||
</div>
|
||||
<div className="bd-vs-score" style={{ marginBottom: 8 }}>
|
||||
<span style={{
|
||||
font: '700 13px \'Rajdhani\', sans-serif',
|
||||
color: '#f67d52',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
<i className="fa fa-star" style={{ fontSize: 11 }} /> {(game.redBonusPoints ?? 0).toFixed(1)}
|
||||
<div className="bd-vs-score bd-bonus-score">
|
||||
<span className="bd-bonus-score__red">
|
||||
<i className="fa fa-star" /> {(game.redBonusPoints ?? 0).toFixed(1)}
|
||||
</span>
|
||||
<span className="bd-vs-score__sep">:</span>
|
||||
<span style={{
|
||||
font: '700 13px \'Rajdhani\', sans-serif',
|
||||
color: '#95cff5',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
{(game.blueBonusPoints ?? 0).toFixed(1)} <i className="fa fa-star" style={{ fontSize: 11 }} />
|
||||
<span className="bd-bonus-score__blue">
|
||||
{(game.blueBonusPoints ?? 0).toFixed(1)} <i className="fa fa-star" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="bd-vs-label">VS</div>
|
||||
<div
|
||||
className="bd-result-badge"
|
||||
style={{ background: meta.bg, border: `1px solid ${meta.border}`, color: meta.color }}
|
||||
style={{ '--bd-result-bg': meta.bg, '--bd-result-border': meta.border, '--bd-result-color': meta.color }}
|
||||
>
|
||||
<i className={`fa ${meta.icon}`} /> {meta.label}
|
||||
</div>
|
||||
@@ -226,7 +191,33 @@ export default function BattleDialog({ games }) {
|
||||
game={game}
|
||||
/>
|
||||
</div>
|
||||
</Dialog>
|
||||
</StyledDialog>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
BattleDialog.propTypes = {
|
||||
games: array.isRequired,
|
||||
};
|
||||
|
||||
const StyledDialog = styled(Dialog)({
|
||||
'& .MuiDialog-paper': {
|
||||
background: '#07090d',
|
||||
backgroundImage: `
|
||||
linear-gradient(rgba(35, 111, 135, 0.08) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(35, 111, 135, 0.08) 1px, transparent 1px)
|
||||
`,
|
||||
backgroundSize: '46px 46px',
|
||||
border: '1px solid rgba(35, 111, 135, 0.4)',
|
||||
borderRadius: '12px',
|
||||
boxShadow: '0 0 80px rgba(35, 111, 135, 0.15), 0 32px 80px rgba(0,0,0,0.9)',
|
||||
width: '580px',
|
||||
maxWidth: '94vw',
|
||||
overflow: 'hidden',
|
||||
color: '#fff',
|
||||
},
|
||||
'& .MuiBackdrop-root': {
|
||||
background: 'rgba(2, 4, 8, 0.88)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
/**
|
||||
* ContactForm Component
|
||||
*
|
||||
* Handles reCAPTCHA v3 integration for the contact form.
|
||||
* Intercepts form submission, executes reCAPTCHA, and submits the form with the token.
|
||||
*
|
||||
* @param {string} siteKey - Google reCAPTCHA site key
|
||||
* @param {string} recaptchaFieldId - ID of the hidden recaptcha input field
|
||||
*/
|
||||
const ContactForm = ({ siteKey, recaptchaFieldId }) => {
|
||||
const formRef = useRef(null);
|
||||
const isSubmittingRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
const form = document.querySelector('.auth-form');
|
||||
|
||||
if (!form) {
|
||||
console.warn('ContactForm: No .auth-form found');
|
||||
return;
|
||||
}
|
||||
|
||||
formRef.current = form;
|
||||
|
||||
const handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
|
||||
if (isSubmittingRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
isSubmittingRef.current = true;
|
||||
|
||||
if ('undefined' !== typeof grecaptcha) {
|
||||
grecaptcha.ready(() => {
|
||||
grecaptcha
|
||||
.execute(siteKey, { action: 'contact' })
|
||||
.then(token => {
|
||||
const recaptchaField = document.getElementById(recaptchaFieldId);
|
||||
|
||||
if (recaptchaField) {
|
||||
recaptchaField.value = token;
|
||||
} else {
|
||||
console.error(`ContactForm: Recaptcha field with ID "${recaptchaFieldId}" not found`);
|
||||
}
|
||||
|
||||
isSubmittingRef.current = false;
|
||||
form.submit();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('ContactForm: reCAPTCHA execution failed', error);
|
||||
isSubmittingRef.current = false;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.error('ContactForm: grecaptcha is not loaded');
|
||||
isSubmittingRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
form.addEventListener('submit', handleSubmit);
|
||||
|
||||
return () => {
|
||||
if (formRef.current) {
|
||||
formRef.current.removeEventListener('submit', handleSubmit);
|
||||
}
|
||||
};
|
||||
}, [siteKey, recaptchaFieldId]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default ContactForm;
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { shape, string } from 'prop-types';
|
||||
|
||||
const base64ToArrayBuffer = base64 => {
|
||||
const binary = atob(base64.replace(/([-_])/g, m => ('-' === m ? '+' : '/')));
|
||||
@@ -108,3 +109,10 @@ const PasskeyLogin = ({ apiRoutes }) => {
|
||||
};
|
||||
|
||||
export default PasskeyLogin;
|
||||
|
||||
PasskeyLogin.propTypes = {
|
||||
apiRoutes: shape({
|
||||
authenticationBegin: string.isRequired,
|
||||
authenticationComplete: string.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
@@ -9,13 +9,15 @@
|
||||
|
||||
import React, { Fragment, useCallback, useEffect, useState } from 'react';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import Button from '@mui/material/Button';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import { arrayOf, shape, string, bool } from 'prop-types';
|
||||
|
||||
const DIALOG_SX = {
|
||||
const StyledDialog = styled(Dialog)({
|
||||
'& .MuiDialog-paper': {
|
||||
background: '#0a0e14',
|
||||
color: '#e0e0e0',
|
||||
@@ -47,7 +49,7 @@ const DIALOG_SX = {
|
||||
background: 'rgba(2, 4, 8, 0.88)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const base64ToArrayBuffer = base64 => {
|
||||
const binary = atob(base64.replace(/([-_])/g, m => ('-' === m ? '+' : '/')));
|
||||
@@ -314,7 +316,7 @@ const PasskeyManager = ({ credentials, apiRoutes }) => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Dialog open={addModalOpen} onClose={closeAddModal} sx={DIALOG_SX}>
|
||||
<StyledDialog open={addModalOpen} onClose={closeAddModal}>
|
||||
<DialogTitle>Add New Passkey</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
@@ -344,9 +346,9 @@ const PasskeyManager = ({ credentials, apiRoutes }) => {
|
||||
Continue
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</StyledDialog>
|
||||
|
||||
<Dialog open={renameModalOpen} onClose={closeRenameModal} sx={DIALOG_SX}>
|
||||
<StyledDialog open={renameModalOpen} onClose={closeRenameModal}>
|
||||
<DialogTitle>Rename Passkey</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
@@ -374,9 +376,9 @@ const PasskeyManager = ({ credentials, apiRoutes }) => {
|
||||
Rename
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</StyledDialog>
|
||||
|
||||
<Dialog open={deleteModalOpen} onClose={closeDeleteModal} sx={DIALOG_SX}>
|
||||
<StyledDialog open={deleteModalOpen} onClose={closeDeleteModal}>
|
||||
<DialogTitle>Delete Passkey</DialogTitle>
|
||||
<DialogContent>
|
||||
<p>
|
||||
@@ -402,9 +404,25 @@ const PasskeyManager = ({ credentials, apiRoutes }) => {
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</StyledDialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PasskeyManager;
|
||||
|
||||
PasskeyManager.propTypes = {
|
||||
credentials: arrayOf(shape({
|
||||
id: string.isRequired,
|
||||
credentialName: string.isRequired,
|
||||
createdAt: string,
|
||||
lastUsedAt: string,
|
||||
isBackupEligible: bool,
|
||||
isBackupAuthenticated: bool,
|
||||
})).isRequired,
|
||||
apiRoutes: shape({
|
||||
credentials: string.isRequired,
|
||||
registrationBegin: string.isRequired,
|
||||
registrationComplete: string.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { BarChart } from '@mui/x-charts/BarChart';
|
||||
import { LineChart } from '@mui/x-charts/LineChart';
|
||||
import { PieChart } from '@mui/x-charts/PieChart';
|
||||
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||||
import { shape, arrayOf, number, string } from 'prop-types';
|
||||
|
||||
const darkTheme = createTheme({
|
||||
palette: {
|
||||
@@ -136,3 +146,20 @@ export default function ProfileCharts({ chartData }) {
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
ProfileCharts.propTypes = {
|
||||
chartData: shape({
|
||||
months: arrayOf(string).isRequired,
|
||||
wins: arrayOf(number).isRequired,
|
||||
losses: arrayOf(number).isRequired,
|
||||
draws: arrayOf(number).isRequired,
|
||||
pieWins: number.isRequired,
|
||||
pieLosses: number.isRequired,
|
||||
pieDraws: number.isRequired,
|
||||
recentGames: shape({
|
||||
labels: arrayOf(string).isRequired,
|
||||
mines: arrayOf(number).isRequired,
|
||||
bonus: arrayOf(number).isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
@@ -7,92 +7,48 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { string } from 'prop-types';
|
||||
|
||||
export default function Avatar({ name, color, avatarUrl, bonusPoints = 0 }) {
|
||||
export const Avatar = ({ name, color, avatarUrl, bonusPoints = 0 }) => {
|
||||
const isRed = 'red' === color;
|
||||
const initials = (name || '?').slice(0, 2).toUpperCase();
|
||||
const initials = useMemo(() => (name || '?').slice(0, 2).toUpperCase(), [name]);
|
||||
|
||||
const gradient = isRed
|
||||
? 'linear-gradient(135deg, rgba(173,10,5,0.6) 0%, rgba(246,125,82,0.4) 100%)'
|
||||
: 'linear-gradient(135deg, rgba(35,111,135,0.6) 0%, rgba(41,128,185,0.4) 100%)';
|
||||
const glow = isRed
|
||||
? '0 0 0 3px rgba(173,10,5,0.2), 0 0 28px rgba(173,10,5,0.35)'
|
||||
: '0 0 0 3px rgba(35,111,135,0.2), 0 0 28px rgba(35,111,135,0.35)';
|
||||
const border = isRed
|
||||
? 'rgba(173,10,5,0.5)'
|
||||
: 'rgba(35,111,135,0.5)';
|
||||
const textColor = isRed ? '#f67d52' : '#95cff5';
|
||||
const cssVars = isRed ? {
|
||||
'--bd-avatar-gradient': 'linear-gradient(135deg, rgba(173,10,5,0.6) 0%, rgba(246,125,82,0.4) 100%)',
|
||||
'--bd-avatar-glow': '0 0 0 3px rgba(173,10,5,0.2), 0 0 28px rgba(173,10,5,0.35)',
|
||||
'--bd-avatar-border': 'rgba(173,10,5,0.5)',
|
||||
'--bd-avatar-color': '#f67d52',
|
||||
} : {
|
||||
'--bd-avatar-gradient': 'linear-gradient(135deg, rgba(35,111,135,0.6) 0%, rgba(41,128,185,0.4) 100%)',
|
||||
'--bd-avatar-glow': '0 0 0 3px rgba(35,111,135,0.2), 0 0 28px rgba(35,111,135,0.35)',
|
||||
'--bd-avatar-border': 'rgba(35,111,135,0.5)',
|
||||
'--bd-avatar-color': '#95cff5',
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10, position: 'relative' }}>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<div style={{
|
||||
width: 72, height: 72, borderRadius: '50%',
|
||||
background: avatarUrl ? 'transparent' : gradient,
|
||||
border: `2px solid ${border}`,
|
||||
boxShadow: glow,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
font: '800 24px \'Rajdhani\', sans-serif',
|
||||
color: textColor,
|
||||
letterSpacing: 2,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{avatarUrl ? (
|
||||
<img
|
||||
src={avatarUrl}
|
||||
alt={name}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
initials
|
||||
)}
|
||||
<div className="bd-avatar-wrap" style={cssVars}>
|
||||
<div className="bd-avatar-ring-wrap">
|
||||
<div className="bd-avatar-ring">
|
||||
{avatarUrl
|
||||
? <img src={avatarUrl} alt={name} className="bd-avatar-img" />
|
||||
: initials}
|
||||
</div>
|
||||
{0 < bonusPoints && (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: -6,
|
||||
right: -6,
|
||||
background: '#ffd700',
|
||||
borderRadius: '50%',
|
||||
width: 28,
|
||||
height: 28,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 0 12px rgba(255, 215, 0, 0.6), 0 0 0 2px rgba(7, 9, 13, 1)',
|
||||
border: '2px solid rgba(0,0,0,0.5)',
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
<i className="fa fa-star" style={{ color: '#000', fontSize: 14 }} />
|
||||
<div className="bd-avatar-bonus">
|
||||
<i className="fa fa-star" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span style={{
|
||||
font: '700 15px \'Rajdhani\', sans-serif',
|
||||
color: textColor,
|
||||
letterSpacing: 1,
|
||||
maxWidth: 120, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
<span style={{
|
||||
font: '600 10px \'Rajdhani\', sans-serif',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 2,
|
||||
color: 'rgba(255,255,255,0.3)',
|
||||
}}
|
||||
>
|
||||
{isRed ? 'Red' : 'Blue'}
|
||||
</span>
|
||||
<span className="bd-avatar-name">{name}</span>
|
||||
<span className="bd-avatar-side">{isRed ? 'Red' : 'Blue'}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Avatar.propTypes = {
|
||||
name: string,
|
||||
color: string,
|
||||
avatarUrl: string,
|
||||
bonusPoints: string,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
import { useMemo } from 'react';
|
||||
import StatRow from './StatRow';
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
export default function BonusPoints({ game }) {
|
||||
import { useMemo } from 'react';
|
||||
import { StatRow } from './StatRow';
|
||||
import { object } from 'prop-types';
|
||||
|
||||
export const BonusPoints = ({ game }) => {
|
||||
const hasBonuspoints = useMemo(
|
||||
() => 0 < game?.redBonusPoints
|
||||
|| 0 < game?.blueBonusPoints
|
||||
@@ -45,95 +55,68 @@ export default function BonusPoints({ game }) {
|
||||
],
|
||||
);
|
||||
|
||||
if (!hasBonuspoints) {
|
||||
return '';
|
||||
}
|
||||
if (!hasBonuspoints) return '';
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
padding: '16px 20px 0',
|
||||
borderTop: '1px solid rgba(255,255,255,0.08)',
|
||||
marginTop: 16,
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
|
||||
{/* Red Bonus */}
|
||||
<div style={{
|
||||
padding: 16,
|
||||
border: '1px solid rgba(173,10,5,0.2)',
|
||||
borderRadius: 6,
|
||||
background: 'rgba(173,10,5,0.05)',
|
||||
}}
|
||||
>
|
||||
<span style={{
|
||||
font: '700 12px \'Rajdhani\', sans-serif',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 2,
|
||||
color: '#ffd700',
|
||||
display: 'block',
|
||||
marginBottom: 12,
|
||||
}}
|
||||
>
|
||||
<i className="fa fa-star" style={{ marginRight: 8 }} /> Red Bonus Statistics
|
||||
<div className="bd-bonus">
|
||||
<div className="bd-bonus__grid">
|
||||
<div className="bd-bonus__column bd-bonus__column--red">
|
||||
<span className="bd-bonus__heading">
|
||||
<i className="fa fa-star" /> Red Bonus Statistics
|
||||
</span>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 0 }}>
|
||||
<StatRow
|
||||
icon="fa-star" label="Total Bonus Points" value={(game.redBonusPoints ?? 0).toFixed(1)}
|
||||
valueColor="#ffd700"
|
||||
/>
|
||||
{0 < game.redBonusStats?.blindHits
|
||||
&& <StatRow icon="fa-bullseye" label="Blind hits" value={game.redBonusStats.blindHits} />}
|
||||
{0 < game.redBonusStats?.chainBest
|
||||
&& <StatRow icon="fa-link" label="Best chain" value={game.redBonusStats.chainBest} />}
|
||||
{0 < game.redBonusStats?.edgeMines
|
||||
&& <StatRow icon="fa-border-all" label="Edge mines" value={game.redBonusStats.edgeMines} />}
|
||||
{0 < game.redBonusStats?.lastMineHits
|
||||
&& <StatRow icon="fa-hourglass-end" label="Endgame mines" value={game.redBonusStats.lastMineHits} />}
|
||||
{0 < game.redBonusStats?.biggestReveal
|
||||
&& <StatRow icon="fa-expand" label="Biggest reveal" value={game.redBonusStats.biggestReveal} />}
|
||||
{hasRedNoBonuses
|
||||
&& <StatRow icon="fa-minus-circle" label="Status" value="No bonuses" valueColor="rgba(255,255,255,0.3)" />}
|
||||
<div className="bd-bonus__rows">
|
||||
<StatRow icon="fa-star" label="Total Bonus Points" value={(game.redBonusPoints ?? 0).toFixed(1)} valueColor="#ffd700" />
|
||||
{0 < game.redBonusStats?.blindHits && (
|
||||
<StatRow icon="fa-bullseye" label="Blind hits" value={game.redBonusStats.blindHits} />
|
||||
)}
|
||||
{0 < game.redBonusStats?.chainBest && (
|
||||
<StatRow icon="fa-link" label="Best chain" value={game.redBonusStats.chainBest} />
|
||||
)}
|
||||
{0 < game.redBonusStats?.edgeMines && (
|
||||
<StatRow icon="fa-border-all" label="Edge mines" value={game.redBonusStats.edgeMines} />
|
||||
)}
|
||||
{0 < game.redBonusStats?.lastMineHits && (
|
||||
<StatRow icon="fa-hourglass-end" label="Endgame mines" value={game.redBonusStats.lastMineHits} />
|
||||
)}
|
||||
{0 < game.redBonusStats?.biggestReveal && (
|
||||
<StatRow icon="fa-expand" label="Biggest reveal" value={game.redBonusStats.biggestReveal} />
|
||||
)}
|
||||
{hasRedNoBonuses && (
|
||||
<StatRow icon="fa-minus-circle" label="Status" value="No bonuses" valueColor="rgba(255,255,255,0.3)" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{
|
||||
padding: 16,
|
||||
border: '1px solid rgba(149,207,245,0.2)',
|
||||
borderRadius: 6,
|
||||
background: 'rgba(149,207,245,0.05)',
|
||||
}}
|
||||
>
|
||||
<span style={{
|
||||
font: '700 12px \'Rajdhani\', sans-serif',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 2,
|
||||
color: '#ffd700',
|
||||
display: 'block',
|
||||
marginBottom: 12,
|
||||
}}
|
||||
>
|
||||
<i className="fa fa-star" style={{ marginRight: 8 }} /> Blue Bonus Statistics
|
||||
<div className="bd-bonus__column bd-bonus__column--blue">
|
||||
<span className="bd-bonus__heading">
|
||||
<i className="fa fa-star" /> Blue Bonus Statistics
|
||||
</span>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 0 }}>
|
||||
<StatRow
|
||||
icon="fa-star" label="Total Bonus Points" value={(game.blueBonusPoints ?? 0).toFixed(1)}
|
||||
valueColor="#ffd700"
|
||||
/>
|
||||
{0 < game.blueBonusStats?.blindHits
|
||||
&& <StatRow icon="fa-bullseye" label="Blind hits" value={game.blueBonusStats.blindHits} />}
|
||||
{0 < game.blueBonusStats?.chainBest
|
||||
&& <StatRow icon="fa-link" label="Best chain" value={game.blueBonusStats.chainBest} />}
|
||||
{0 < game.blueBonusStats?.edgeMines
|
||||
&& <StatRow icon="fa-border-all" label="Edge mines" value={game.blueBonusStats.edgeMines} />}
|
||||
{0 < game.blueBonusStats?.lastMineHits
|
||||
&& <StatRow icon="fa-hourglass-end" label="Endgame mines" value={game.blueBonusStats.lastMineHits} />}
|
||||
{0 < game.blueBonusStats?.biggestReveal
|
||||
&& <StatRow icon="fa-expand" label="Biggest reveal" value={game.blueBonusStats.biggestReveal} />}
|
||||
{hasBlueNoBonuses
|
||||
&& <StatRow icon="fa-minus-circle" label="Status" value="No bonuses" valueColor="rgba(255,255,255,0.3)" />}
|
||||
<div className="bd-bonus__rows">
|
||||
<StatRow icon="fa-star" label="Total Bonus Points" value={(game.blueBonusPoints ?? 0).toFixed(1)} valueColor="#ffd700" />
|
||||
{0 < game.blueBonusStats?.blindHits && (
|
||||
<StatRow icon="fa-bullseye" label="Blind hits" value={game.blueBonusStats.blindHits} />
|
||||
)}
|
||||
{0 < game.blueBonusStats?.chainBest && (
|
||||
<StatRow icon="fa-link" label="Best chain" value={game.blueBonusStats.chainBest} />
|
||||
)}
|
||||
{0 < game.blueBonusStats?.edgeMines && (
|
||||
<StatRow icon="fa-border-all" label="Edge mines" value={game.blueBonusStats.edgeMines} />
|
||||
)}
|
||||
{0 < game.blueBonusStats?.lastMineHits && (
|
||||
<StatRow icon="fa-hourglass-end" label="Endgame mines" value={game.blueBonusStats.lastMineHits} />
|
||||
)}
|
||||
{0 < game.blueBonusStats?.biggestReveal && (
|
||||
<StatRow icon="fa-expand" label="Biggest reveal" value={game.blueBonusStats.biggestReveal} />
|
||||
)}
|
||||
{hasBlueNoBonuses && (
|
||||
<StatRow icon="fa-minus-circle" label="Status" value="No bonuses" valueColor="rgba(255,255,255,0.3)" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
BonusPoints.propTypes = {
|
||||
game: object.isRequired,
|
||||
};
|
||||
|
||||
@@ -8,33 +8,24 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { node, string } from 'prop-types';
|
||||
|
||||
export default function StatRow({ icon, label, value, valueColor }) {
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'center',
|
||||
gap: 10, padding: '9px 0',
|
||||
borderBottom: '1px solid rgba(255,255,255,0.05)',
|
||||
}}
|
||||
export const StatRow = ({ icon, label, value, valueColor }) => (
|
||||
<div className="bd-stat-row">
|
||||
<i className={`fa ${icon} bd-stat-row__icon`} />
|
||||
<span className="bd-stat-row__label">{label}</span>
|
||||
<span
|
||||
className="bd-stat-row__value"
|
||||
style={valueColor ? { '--bd-stat-value-color': valueColor } : undefined}
|
||||
>
|
||||
<i className={`fa ${icon}`} style={{ width: 16, color: 'rgba(149,207,245,0.4)', fontSize: 13 }} />
|
||||
<span style={{
|
||||
font: '500 13px \'Rajdhani\', sans-serif',
|
||||
color: 'rgba(255,255,255,0.45)',
|
||||
flex: 1,
|
||||
letterSpacing: 0.5,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
<span style={{
|
||||
font: '700 13px \'Rajdhani\', sans-serif',
|
||||
color: valueColor || 'rgba(255,255,255,0.75)',
|
||||
letterSpacing: 0.5,
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
StatRow.propTypes = {
|
||||
icon: string.isRequired,
|
||||
label: string.isRequired,
|
||||
value: node.isRequired,
|
||||
valueColor: string,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
export { AvatarUpload } from './AvatarUpload';
|
||||
export { BattleDialog } from './BattleDialog';
|
||||
export { default as PasskeyLogin } from './PasskeyLogin';
|
||||
export { default as PasskeyManager } from './PasskeyManager';
|
||||
export { default as ProfileCharts } from './ProfileCharts';
|
||||
export { BonusPoints } from './battle-dialog/BonusPoints';
|
||||
export { Avatar } from './battle-dialog/Avatar';
|
||||
export { StatRow } from './battle-dialog/StatRow';
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import ContactForm from './components/ContactForm';
|
||||
|
||||
const wrapper = document.getElementById('contact-form-wrapper');
|
||||
|
||||
if (wrapper) {
|
||||
const siteKey = wrapper.dataset.siteKey;
|
||||
const recaptchaFieldId = wrapper.dataset.recaptchaFieldId;
|
||||
|
||||
if (siteKey && recaptchaFieldId) {
|
||||
createRoot(wrapper).render(
|
||||
<ContactForm
|
||||
siteKey={siteKey}
|
||||
recaptchaFieldId={recaptchaFieldId}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
console.error('ContactForm: Missing siteKey or recaptchaFieldId in data attributes');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import React, { useRef } from 'react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { GameProvider } from '@mine-contexts';
|
||||
import { GameBoard } from '@mine-components';
|
||||
import { string } from 'prop-types';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
@@ -34,3 +35,9 @@ const MineSeeker = ({ env, gameId, opponentName = '' }) => {
|
||||
};
|
||||
|
||||
export default MineSeeker;
|
||||
|
||||
MineSeeker.propTypes = {
|
||||
env: string.isRequired,
|
||||
gameId: string,
|
||||
opponentName: string,
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { func, number, string } from 'prop-types';
|
||||
|
||||
const BonusBox = ({ color, points, onClick, title }) => (
|
||||
<button
|
||||
@@ -23,3 +24,10 @@ const BonusBox = ({ color, points, onClick, title }) => (
|
||||
);
|
||||
|
||||
export default BonusBox;
|
||||
|
||||
BonusBox.propTypes = {
|
||||
color: string.isRequired,
|
||||
points: number.isRequired,
|
||||
onClick: func.isRequired,
|
||||
title: string,
|
||||
};
|
||||
|
||||
@@ -9,67 +9,12 @@
|
||||
|
||||
import React from 'react';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import { BONUS_LABELS } from '@mine-utils';
|
||||
|
||||
const DIALOG_SX = {
|
||||
'& .MuiDialog-paper': {
|
||||
background: '#07090d',
|
||||
backgroundImage: `
|
||||
linear-gradient(rgba(35, 111, 135, 0.08) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(35, 111, 135, 0.08) 1px, transparent 1px)
|
||||
`,
|
||||
backgroundSize: '46px 46px',
|
||||
border: '1px solid rgba(35, 111, 135, 0.4)',
|
||||
borderRadius: '12px',
|
||||
boxShadow: '0 0 80px rgba(35, 111, 135, 0.15), 0 32px 80px rgba(0, 0, 0, 0.9)',
|
||||
width: '560px',
|
||||
maxWidth: '94vw',
|
||||
overflow: 'hidden',
|
||||
color: '#fff',
|
||||
},
|
||||
'& .MuiBackdrop-root': {
|
||||
background: 'rgba(2, 4, 8, 0.88)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
},
|
||||
};
|
||||
|
||||
const formatPlayerName = name => {
|
||||
if (name && name.startsWith('anon_')) {
|
||||
return 'Anonymous';
|
||||
}
|
||||
|
||||
if (name && 10 < name.length) {
|
||||
return name.substring(0, 7) + '...';
|
||||
}
|
||||
|
||||
return name || 'Unknown';
|
||||
};
|
||||
|
||||
const PlayerColumn = ({ color, player }) => (
|
||||
<div className={`bsd-column bsd-column--${color}`}>
|
||||
<div className="bsd-column-header">
|
||||
<span className="bsd-column-name">{formatPlayerName(player.name)}</span>
|
||||
<span className="bsd-column-total">
|
||||
<i className="fa fa-star" />
|
||||
{player.bonusPoints}
|
||||
</span>
|
||||
</div>
|
||||
<ul className="bsd-stats">
|
||||
{Object.entries(BONUS_LABELS).map(([key, { label, desc }]) => (
|
||||
<li key={key} className="bsd-stat">
|
||||
<div className="bsd-stat-text">
|
||||
<span className="bsd-stat-label">{label}</span>
|
||||
<span className="bsd-stat-desc">{desc}</span>
|
||||
</div>
|
||||
<span className="bsd-stat-value">{player.bonusStats?.[key] ?? 0}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { PlayerColumn } from '@mine-components';
|
||||
import { bool, func, shape, string, number, object } from 'prop-types';
|
||||
|
||||
const BonusStatsDialog = ({ open, onClose, red, blue }) => (
|
||||
<Dialog open={open} onClose={onClose} sx={DIALOG_SX}>
|
||||
<StyledDialog open={open} onClose={onClose}>
|
||||
<div className="bsd">
|
||||
<div className="bsd-header">
|
||||
<div className="bsd-header-text">
|
||||
@@ -91,7 +36,44 @@ const BonusStatsDialog = ({ open, onClose, red, blue }) => (
|
||||
Bonus points are awarded alongside the main score for skillful play.
|
||||
</p>
|
||||
</div>
|
||||
</Dialog>
|
||||
</StyledDialog>
|
||||
);
|
||||
|
||||
const StyledDialog = styled(Dialog)({
|
||||
'& .MuiDialog-paper': {
|
||||
background: '#07090d',
|
||||
backgroundImage: `
|
||||
linear-gradient(rgba(35, 111, 135, 0.08) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(35, 111, 135, 0.08) 1px, transparent 1px)
|
||||
`,
|
||||
backgroundSize: '46px 46px',
|
||||
border: '1px solid rgba(35, 111, 135, 0.4)',
|
||||
borderRadius: '12px',
|
||||
boxShadow: '0 0 80px rgba(35, 111, 135, 0.15), 0 32px 80px rgba(0, 0, 0, 0.9)',
|
||||
width: '560px',
|
||||
maxWidth: '94vw',
|
||||
overflow: 'hidden',
|
||||
color: '#fff',
|
||||
},
|
||||
'& .MuiBackdrop-root': {
|
||||
background: 'rgba(2, 4, 8, 0.88)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
},
|
||||
});
|
||||
|
||||
export default BonusStatsDialog;
|
||||
|
||||
BonusStatsDialog.propTypes = {
|
||||
open: bool.isRequired,
|
||||
onClose: func.isRequired,
|
||||
red: shape({
|
||||
name: string,
|
||||
bonusPoints: number,
|
||||
bonusStats: object,
|
||||
}).isRequired,
|
||||
blue: shape({
|
||||
name: string,
|
||||
bonusPoints: number,
|
||||
bonusStats: object,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
@@ -7,16 +7,16 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import 'cap-widget';
|
||||
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { func, node, string } from 'prop-types';
|
||||
|
||||
const CAPTCHA_STORAGE_KEY = 'mineseeker_captcha_verified';
|
||||
const CAPTCHA_TOKEN_KEY = 'mineseeker_captcha_token';
|
||||
const RECAPTCHA_ACTION = 'mineseeker_play';
|
||||
|
||||
const CaptchaOverlay = ({ siteKey, onVerified, children }) => {
|
||||
const CaptchaOverlay = ({ apiEndpoint, onVerified, children }) => {
|
||||
const [verified, setVerified] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
const capRef = useRef(null);
|
||||
|
||||
const handleToken = useCallback(token => {
|
||||
const wrapper = document.getElementById('mine-wrapper');
|
||||
@@ -29,12 +29,6 @@ const CaptchaOverlay = ({ siteKey, onVerified, children }) => {
|
||||
onVerified?.();
|
||||
}, [onVerified]);
|
||||
|
||||
const buttonClasses = useMemo(() => [
|
||||
'captcha-button',
|
||||
error && 'captcha-button--error',
|
||||
loading && 'captcha-button--loading',
|
||||
].filter(Boolean).join(' '), [error, loading]);
|
||||
|
||||
useEffect(() => {
|
||||
const storedToken = sessionStorage.getItem(CAPTCHA_TOKEN_KEY);
|
||||
const storedTime = sessionStorage.getItem(CAPTCHA_STORAGE_KEY);
|
||||
@@ -51,43 +45,45 @@ const CaptchaOverlay = ({ siteKey, onVerified, children }) => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, [onVerified]);
|
||||
|
||||
if (window.grecaptcha) {
|
||||
window.grecaptcha.ready(() => {
|
||||
window.grecaptcha
|
||||
.execute(siteKey, { action: RECAPTCHA_ACTION })
|
||||
.then(token => {
|
||||
handleToken(token);
|
||||
})
|
||||
.catch(() => {
|
||||
setError(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [siteKey, onVerified, handleToken]);
|
||||
useEffect(() => {
|
||||
const widget = document.createElement('cap-widget');
|
||||
widget.style.display = 'none';
|
||||
capRef.current = widget;
|
||||
document.body.appendChild(widget);
|
||||
|
||||
const cap = new window.Cap({ apiEndpoint }, widget);
|
||||
let cancelled = false;
|
||||
|
||||
const handleClick = () => {
|
||||
setLoading(true);
|
||||
setError(false);
|
||||
const run = async () => {
|
||||
try {
|
||||
const result = await cap.solve();
|
||||
if (!cancelled && result?.token) {
|
||||
handleToken(result.token);
|
||||
}
|
||||
} catch (_) {
|
||||
if (!cancelled) {
|
||||
setTimeout(() => {
|
||||
if (!cancelled) {
|
||||
run();
|
||||
}
|
||||
}, 1200);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.grecaptcha.ready(() => {
|
||||
window.grecaptcha
|
||||
.execute(siteKey, { action: RECAPTCHA_ACTION })
|
||||
.then(token => {
|
||||
handleToken(token);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
setError(true);
|
||||
setTimeout(() => setError(false), 2000);
|
||||
});
|
||||
});
|
||||
};
|
||||
run();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
widget.remove();
|
||||
capRef.current = null;
|
||||
};
|
||||
}, [apiEndpoint, handleToken]);
|
||||
|
||||
if (verified) {
|
||||
return <>{children}</>;
|
||||
return <Fragment>{children}</Fragment>;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -98,19 +94,17 @@ const CaptchaOverlay = ({ siteKey, onVerified, children }) => {
|
||||
</div>
|
||||
<h1 className="captcha-title">Ready to Play?</h1>
|
||||
<p className="captcha-description">
|
||||
Click below to verify you're human and start playing.
|
||||
Verifying your session...
|
||||
</p>
|
||||
<button
|
||||
className={buttonClasses}
|
||||
onClick={handleClick}
|
||||
disabled={loading}
|
||||
>
|
||||
<i className={`fa ${loading ? 'fa-spinner fa-spin' : error ? 'fa-exclamation-circle' : 'fa-play'}`} />
|
||||
{loading ? 'Verifying...' : error ? 'Try Again' : 'Start Playing'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CaptchaOverlay;
|
||||
|
||||
CaptchaOverlay.propTypes = {
|
||||
apiEndpoint: string.isRequired,
|
||||
onVerified: func,
|
||||
children: node,
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
import { func, number } from 'prop-types';
|
||||
|
||||
const ChallengeCountdown = ({ onAccept, onDecline, seconds = 30 }) => {
|
||||
const [countdown, setCountdown] = useState(seconds);
|
||||
@@ -39,3 +40,9 @@ const ChallengeCountdown = ({ onAccept, onDecline, seconds = 30 }) => {
|
||||
};
|
||||
|
||||
export default ChallengeCountdown;
|
||||
|
||||
ChallengeCountdown.propTypes = {
|
||||
onAccept: func.isRequired,
|
||||
onDecline: func.isRequired,
|
||||
seconds: number,
|
||||
};
|
||||
|
||||
@@ -12,13 +12,14 @@ import { useGame } from '@mine-contexts';
|
||||
import { useServerCommunication } from '@mine-hooks';
|
||||
import CaptchaOverlay from './CaptchaOverlay';
|
||||
import GridControl from './grid/GridControl';
|
||||
import { bool, string } from 'prop-types';
|
||||
|
||||
export const GameBoard = ({ gameAssoc, gameInherited, opponentName = '', isEnvDev }) => {
|
||||
const { gridReady } = useGame();
|
||||
const { onClick, resign } = useServerCommunication(gameAssoc, gameInherited, opponentName, isEnvDev);
|
||||
const [captchaVerified, setCaptchaVerified] = useState(false);
|
||||
|
||||
const siteKey = document.getElementById('mine-wrapper')?.dataset.recaptchaSiteKey;
|
||||
const apiEndpoint = document.getElementById('mine-wrapper')?.dataset.capApiEndpoint;
|
||||
|
||||
if (!gridReady) {
|
||||
return (
|
||||
@@ -28,9 +29,9 @@ export const GameBoard = ({ gameAssoc, gameInherited, opponentName = '', isEnvDe
|
||||
);
|
||||
}
|
||||
|
||||
if (!captchaVerified && siteKey) {
|
||||
if (!captchaVerified && apiEndpoint) {
|
||||
return (
|
||||
<CaptchaOverlay siteKey={siteKey} onVerified={() => setCaptchaVerified(true)} />
|
||||
<CaptchaOverlay apiEndpoint={apiEndpoint} onVerified={() => setCaptchaVerified(true)} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,3 +43,10 @@ export const GameBoard = ({ gameAssoc, gameInherited, opponentName = '', isEnvDe
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
GameBoard.propTypes = {
|
||||
gameAssoc: string.isRequired,
|
||||
gameInherited: bool.isRequired,
|
||||
opponentName: string,
|
||||
isEnvDev: bool,
|
||||
};
|
||||
|
||||
@@ -7,22 +7,10 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { BonusBox, BonusStatsDialog, Avatar } from '@mine-components';
|
||||
import { formatTime } from '@global-utils/format';
|
||||
import { useGame } from '@mine-contexts';
|
||||
import BonusBox from './BonusBox';
|
||||
import BonusStatsDialog from './BonusStatsDialog';
|
||||
|
||||
const renderAvatar = player => {
|
||||
if (!player.registered) return null;
|
||||
return (
|
||||
<div className="timer-avatar">
|
||||
{player.avatar
|
||||
? <img src={player.avatar} alt={player.name} className="timer-avatar__img" />
|
||||
: <span className="timer-avatar__initials">{player.name.slice(0, 2).toUpperCase()}</span>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const GameTimer = () => {
|
||||
const { overlay, connectionLost, endRef, activePlayer, red, blue } = useGame();
|
||||
@@ -85,84 +73,61 @@ const GameTimer = () => {
|
||||
}
|
||||
}, [activePlayer, isRunning]);
|
||||
|
||||
const syncTimes = useCallback(() => {
|
||||
let currentRedTime = pausedRedTimeRef.current;
|
||||
let currentBlueTime = pausedBlueTimeRef.current;
|
||||
|
||||
if (!activePlayer && redStartTimeRef.current) {
|
||||
currentRedTime += Math.floor((Date.now() - redStartTimeRef.current) / 1000);
|
||||
} else if (activePlayer && blueStartTimeRef.current) {
|
||||
currentBlueTime += Math.floor((Date.now() - blueStartTimeRef.current) / 1000);
|
||||
}
|
||||
|
||||
setRedTime(currentRedTime);
|
||||
setBlueTime(currentBlueTime);
|
||||
}, [activePlayer]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isRunning) {
|
||||
if (timerIntervalRef.current) {
|
||||
clearInterval(timerIntervalRef.current);
|
||||
}
|
||||
if (timerIntervalRef.current) clearInterval(timerIntervalRef.current);
|
||||
return;
|
||||
}
|
||||
|
||||
timerIntervalRef.current = setInterval(() => {
|
||||
let currentRedTime = pausedRedTimeRef.current;
|
||||
let currentBlueTime = pausedBlueTimeRef.current;
|
||||
|
||||
if (!activePlayer && redStartTimeRef.current) {
|
||||
currentRedTime += Math.floor((Date.now() - redStartTimeRef.current) / 1000);
|
||||
} else if (activePlayer && blueStartTimeRef.current) {
|
||||
currentBlueTime += Math.floor((Date.now() - blueStartTimeRef.current) / 1000);
|
||||
}
|
||||
|
||||
setRedTime(currentRedTime);
|
||||
setBlueTime(currentBlueTime);
|
||||
}, 100);
|
||||
timerIntervalRef.current = setInterval(syncTimes, 100);
|
||||
|
||||
return () => {
|
||||
if (timerIntervalRef.current) {
|
||||
clearInterval(timerIntervalRef.current);
|
||||
}
|
||||
if (timerIntervalRef.current) clearInterval(timerIntervalRef.current);
|
||||
};
|
||||
}, [isRunning, activePlayer]);
|
||||
}, [isRunning, activePlayer, syncTimes]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleFocus = () => {
|
||||
if (isRunning) {
|
||||
let currentRedTime = pausedRedTimeRef.current;
|
||||
let currentBlueTime = pausedBlueTimeRef.current;
|
||||
|
||||
if (!activePlayer && redStartTimeRef.current) {
|
||||
currentRedTime += Math.floor((Date.now() - redStartTimeRef.current) / 1000);
|
||||
} else if (activePlayer && blueStartTimeRef.current) {
|
||||
currentBlueTime += Math.floor((Date.now() - blueStartTimeRef.current) / 1000);
|
||||
}
|
||||
|
||||
setRedTime(currentRedTime);
|
||||
setBlueTime(currentBlueTime);
|
||||
}
|
||||
if (isRunning) syncTimes();
|
||||
};
|
||||
|
||||
window.addEventListener('focus', handleFocus);
|
||||
return () => window.removeEventListener('focus', handleFocus);
|
||||
}, [isRunning, activePlayer]);
|
||||
}, [isRunning, activePlayer, syncTimes]);
|
||||
|
||||
useEffect(() => () => {
|
||||
if (timerIntervalRef.current) clearInterval(timerIntervalRef.current);
|
||||
}, []);
|
||||
|
||||
const formatTime = seconds => {
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = seconds % 60;
|
||||
return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
const openBonusDialog = () => setBonusDialogOpen(true);
|
||||
const closeBonusDialog = () => setBonusDialogOpen(false);
|
||||
|
||||
return (
|
||||
<div className="game-timer-container">
|
||||
<BonusBox color="red" points={red.bonusPoints ?? 0} onClick={openBonusDialog} />
|
||||
<BonusBox color="red" points={red.bonusPoints ?? 0} onClick={() => setBonusDialogOpen(true)} />
|
||||
<div className={`game-timer red-timer ${!activePlayer ? 'active' : ''}`}>
|
||||
{renderAvatar(red)}
|
||||
<Avatar player={red} />
|
||||
<i className={`fa ${!activePlayer ? 'fa-hourglass-half' : 'fa-hourglass-start'} timer-icon`} />
|
||||
<span className="timer-display">{formatTime(redTime)}</span>
|
||||
</div>
|
||||
<div className={`game-timer blue-timer ${activePlayer ? 'active' : ''}`}>
|
||||
{renderAvatar(blue)}
|
||||
<Avatar player={blue} />
|
||||
<i className={`fa ${activePlayer ? 'fa-hourglass-half' : 'fa-hourglass-start'} timer-icon`} />
|
||||
<span className="timer-display">{formatTime(blueTime)}</span>
|
||||
</div>
|
||||
<BonusBox color="blue" points={blue.bonusPoints ?? 0} onClick={openBonusDialog} />
|
||||
<BonusStatsDialog open={bonusDialogOpen} onClose={closeBonusDialog} red={red} blue={blue} />
|
||||
<BonusBox color="blue" points={blue.bonusPoints ?? 0} onClick={() => setBonusDialogOpen(true)} />
|
||||
<BonusStatsDialog open={bonusDialogOpen} onClose={() => setBonusDialogOpen(false)} red={red} blue={blue} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,37 +8,11 @@
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { formatSince } from '@global-utils/format';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { useLobbyDataProvider } from '@mine-hooks';
|
||||
|
||||
const DIALOG_SX = {
|
||||
'& .MuiDialog-paper': {
|
||||
background: '#07090d',
|
||||
backgroundImage: `
|
||||
linear-gradient(rgba(35, 111, 135, 0.08) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(35, 111, 135, 0.08) 1px, transparent 1px)
|
||||
`,
|
||||
backgroundSize: '46px 46px',
|
||||
border: '1px solid rgba(35, 111, 135, 0.4)',
|
||||
borderRadius: '12px',
|
||||
boxShadow: '0 0 80px rgba(35, 111, 135, 0.15), 0 32px 80px rgba(0, 0, 0, 0.9)',
|
||||
width: '500px',
|
||||
maxWidth: '94vw',
|
||||
overflow: 'hidden',
|
||||
color: '#fff',
|
||||
},
|
||||
'& .MuiBackdrop-root': {
|
||||
background: 'rgba(2, 4, 8, 0.88)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
},
|
||||
};
|
||||
|
||||
const formatSince = isoStr => {
|
||||
const diff = Math.floor((Date.now() - new Date(isoStr)) / 60000);
|
||||
if (1 > diff) return 'just now';
|
||||
if (1 === diff) return '1 min ago';
|
||||
return `${diff} min ago`;
|
||||
};
|
||||
import { bool, func, string } from 'prop-types';
|
||||
|
||||
const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc, isEnvDev = false }) => {
|
||||
const [players, setPlayers] = useState([]);
|
||||
@@ -171,11 +145,9 @@ const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc, isEnvDev = false
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
<StyledDialog
|
||||
open={open}
|
||||
onClose={0 < waitingCountdown ? undefined : onClose}
|
||||
disableEscapeKeyDown={0 < waitingCountdown}
|
||||
sx={DIALOG_SX}
|
||||
>
|
||||
<div className="opd">
|
||||
<div className="opd-header">
|
||||
@@ -286,8 +258,37 @@ const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc, isEnvDev = false
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</Dialog>
|
||||
</StyledDialog>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledDialog = styled(Dialog)({
|
||||
'& .MuiDialog-paper': {
|
||||
background: '#07090d',
|
||||
backgroundImage: `
|
||||
linear-gradient(rgba(35, 111, 135, 0.08) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(35, 111, 135, 0.08) 1px, transparent 1px)
|
||||
`,
|
||||
backgroundSize: '46px 46px',
|
||||
border: '1px solid rgba(35, 111, 135, 0.4)',
|
||||
borderRadius: '12px',
|
||||
boxShadow: '0 0 80px rgba(35, 111, 135, 0.15), 0 32px 80px rgba(0, 0, 0, 0.9)',
|
||||
width: '500px',
|
||||
maxWidth: '94vw',
|
||||
overflow: 'hidden',
|
||||
color: '#fff',
|
||||
},
|
||||
'& .MuiBackdrop-root': {
|
||||
background: 'rgba(2, 4, 8, 0.88)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
},
|
||||
});
|
||||
|
||||
export default OnlinePlayersDialog;
|
||||
|
||||
OnlinePlayersDialog.propTypes = {
|
||||
open: bool.isRequired,
|
||||
onClose: func.isRequired,
|
||||
currentGameAssoc: string,
|
||||
isEnvDev: bool,
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
import { Fragment, useState } from 'react';
|
||||
import { OnlinePlayersDialog } from '@mine-components';
|
||||
import { bool, string } from 'prop-types';
|
||||
|
||||
const WaitingOverlayContent = ({ shareUrl, currentGameAssoc, opponentName = '', inviteOnly = false }) => {
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
@@ -94,3 +95,10 @@ const ShareLinkBox = ({ url }) => {
|
||||
};
|
||||
|
||||
export default WaitingOverlayContent;
|
||||
|
||||
WaitingOverlayContent.propTypes = {
|
||||
shareUrl: string.isRequired,
|
||||
currentGameAssoc: string,
|
||||
opponentName: string,
|
||||
inviteOnly: bool,
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ import GridField from './GridField';
|
||||
import UserControl from '../user/UserControl';
|
||||
import GameTimer from '../GameTimer';
|
||||
import { BOMB_SYMBOLS, bombRadius } from '@mine-utils';
|
||||
import { func, string } from 'prop-types';
|
||||
|
||||
const GridControl = ({ gameAssoc, onClick, resign }) => {
|
||||
const {
|
||||
@@ -23,11 +24,13 @@ const GridControl = ({ gameAssoc, onClick, resign }) => {
|
||||
|
||||
const [copied, setCopied] = useState(false);
|
||||
const shareUrl = gameAssoc ? `${window.location.origin}/play/${gameAssoc}` : null;
|
||||
const endShareUrl = gameAssoc ? `${window.location.origin}/battle/${gameAssoc}` : null;
|
||||
const isAuthenticated = '1' === document.getElementById('mine-wrapper')?.dataset.isAuthenticated;
|
||||
|
||||
const handleShare = () => {
|
||||
if (!shareUrl) return;
|
||||
navigator.clipboard.writeText(shareUrl).then(() => {
|
||||
const url = endRef.current ? endShareUrl : shareUrl;
|
||||
if (!url) return;
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2200);
|
||||
});
|
||||
@@ -59,10 +62,11 @@ const GridControl = ({ gameAssoc, onClick, resign }) => {
|
||||
<div className={`game-overlay${overlay ? '' : ' hide'}`}>
|
||||
<div className="game-overlay-window">
|
||||
<h1>{overlayTitle}</h1>
|
||||
{'string' === typeof overlaySubTitle ? (
|
||||
{'string' === typeof overlaySubTitle && (
|
||||
<h2>{overlaySubTitle}</h2>
|
||||
) : (
|
||||
overlaySubTitle
|
||||
)}
|
||||
{'string' !== typeof overlaySubTitle && (
|
||||
<Fragment>{overlaySubTitle}</Fragment>
|
||||
)}
|
||||
{gameAssoc && endRef.current && (
|
||||
<div className="game-overlay-actions">
|
||||
@@ -111,3 +115,9 @@ const GridControl = ({ gameAssoc, onClick, resign }) => {
|
||||
};
|
||||
|
||||
export default GridControl;
|
||||
|
||||
GridControl.propTypes = {
|
||||
gameAssoc: string,
|
||||
onClick: func.isRequired,
|
||||
resign: func.isRequired,
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { IMAGES } from '@mine-utils';
|
||||
import { func, shape, bool, number, string } from 'prop-types';
|
||||
|
||||
const bombSrc = area => {
|
||||
if (null === area) return null;
|
||||
@@ -75,3 +76,16 @@ const GridField = memo(function GridField({ cell, onClick, onMouseEnter }) {
|
||||
});
|
||||
|
||||
export default GridField;
|
||||
|
||||
GridField.propTypes = {
|
||||
cell: shape({
|
||||
currentImage: string,
|
||||
currentObj: string,
|
||||
active: bool,
|
||||
lastClickedRed: bool,
|
||||
lastClickedBlue: bool,
|
||||
bombTargetArea: number,
|
||||
}).isRequired,
|
||||
onClick: func.isRequired,
|
||||
onMouseEnter: func.isRequired,
|
||||
};
|
||||
|
||||
@@ -16,3 +16,7 @@ export { default as GridControl } from './grid/GridControl';
|
||||
export { default as GridField } from './grid/GridField';
|
||||
export { default as User } from './user/User';
|
||||
export { default as UserControl } from './user/UserControl';
|
||||
export { default as BonusBox } from './BonusBox';
|
||||
export { default as BonusStatsDialog } from './BonusStatsDialog';
|
||||
export { Avatar } from './timer/Avatar';
|
||||
export { PlayerColumn } from './profile/PlayerColumn';
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { object, string } from 'prop-types';
|
||||
import { BONUS_LABELS } from '@mine-utils';
|
||||
|
||||
const formatPlayerName = name => {
|
||||
if (name && name.startsWith('anon_')) {
|
||||
return 'Anonymous';
|
||||
}
|
||||
|
||||
if (name && 10 < name.length) {
|
||||
return name.substring(0, 7) + '...';
|
||||
}
|
||||
|
||||
return name || 'Unknown';
|
||||
};
|
||||
|
||||
export const PlayerColumn = ({ color, player }) => (
|
||||
<div className={`bsd-column bsd-column--${color}`}>
|
||||
<div className="bsd-column-header">
|
||||
<span className="bsd-column-name">{formatPlayerName(player.name)}</span>
|
||||
<span className="bsd-column-total">
|
||||
<i className="fa fa-star" />
|
||||
{player.bonusPoints}
|
||||
</span>
|
||||
</div>
|
||||
<ul className="bsd-stats">
|
||||
{Object.entries(BONUS_LABELS).map(([key, { label, desc }]) => (
|
||||
<li key={key} className="bsd-stat">
|
||||
<div className="bsd-stat-text">
|
||||
<span className="bsd-stat-label">{label}</span>
|
||||
<span className="bsd-stat-desc">{desc}</span>
|
||||
</div>
|
||||
<span className="bsd-stat-value">{player.bonusStats?.[key] ?? 0}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
PlayerColumn.propTypes = {
|
||||
color: string.isRequired,
|
||||
player: object.isRequired,
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { object } from 'prop-types';
|
||||
|
||||
export const Avatar = ({ player }) => {
|
||||
if (!player.registered) return '';
|
||||
|
||||
return (
|
||||
<div className="timer-avatar">
|
||||
{player.avatar
|
||||
? <img src={player.avatar} alt={player.name} className="timer-avatar__img" />
|
||||
: <span className="timer-avatar__initials">{player.name.slice(0, 2).toUpperCase()}</span>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Avatar.propTypes = {
|
||||
player: object.isRequired,
|
||||
};
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { IMAGES } from '@mine-utils';
|
||||
import { bool, func, number, string } from 'prop-types';
|
||||
|
||||
const User = memo(function User(
|
||||
{
|
||||
@@ -52,3 +53,15 @@ const User = memo(function User(
|
||||
});
|
||||
|
||||
export default User;
|
||||
|
||||
User.propTypes = {
|
||||
color: string.isRequired,
|
||||
webPlayer: string,
|
||||
name: string,
|
||||
desc: string,
|
||||
active: bool,
|
||||
mines: number,
|
||||
haveBomb: bool,
|
||||
enabledBomb: bool,
|
||||
onClickBombSelector: func.isRequired,
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ import React, { Fragment, useState } from 'react';
|
||||
import { useGame } from '@mine-contexts';
|
||||
import User from './User';
|
||||
import BonusStatsDialog from '../BonusStatsDialog';
|
||||
import { func } from 'prop-types';
|
||||
|
||||
const UserControl = ({ resign }) => {
|
||||
const { webPlayer, activePlayer, foundMines, red, blue, onBombToggle } = useGame();
|
||||
@@ -69,3 +70,7 @@ const UserControl = ({ resign }) => {
|
||||
}
|
||||
|
||||
export default UserControl;
|
||||
|
||||
UserControl.propTypes = {
|
||||
resign: func.isRequired,
|
||||
};
|
||||
|
||||
@@ -49,6 +49,7 @@ export const GameProvider = ({ children }) => {
|
||||
mine: new Howl({ src: ['/sound/mine.mp3'] }),
|
||||
warning: new Howl({ src: ['/sound/warning.mp3'] }),
|
||||
won: new Howl({ src: ['/sound/won.mp3'] }),
|
||||
starting: new Howl({ src: ['/sound/starting.mp3'] }),
|
||||
});
|
||||
|
||||
/** Sync helpers (keep ref + state in lockstep) */
|
||||
@@ -187,7 +188,7 @@ export const GameProvider = ({ children }) => {
|
||||
syncBlue(p => ({ ...p, mines: 'blue' === player ? bp : p.mines }));
|
||||
}
|
||||
|
||||
// Update bonus points and stats
|
||||
/** Update bonus points and stats */
|
||||
syncRed(p => ({
|
||||
...p,
|
||||
bonusPoints: 'red' === player ? redBonusPoints : p.bonusPoints,
|
||||
@@ -218,7 +219,10 @@ export const GameProvider = ({ children }) => {
|
||||
if (redWins || blueWins || resign) {
|
||||
sounds.current.won.play();
|
||||
|
||||
if (!resign) showOverlay((redWins ? 'Red' : 'Blue') + ' wins the game!', 'Play again!');
|
||||
if (!resign) {
|
||||
endRef.current = true;
|
||||
showOverlay((redWins ? 'Red' : 'Blue') + ' wins the game!', null);
|
||||
}
|
||||
|
||||
showLeftMines(leftMines);
|
||||
syncActivePlayer(false);
|
||||
@@ -251,21 +255,23 @@ export const GameProvider = ({ children }) => {
|
||||
return (
|
||||
<GameContext.Provider
|
||||
value={{
|
||||
// State (for rendering)
|
||||
/** State (for rendering) */
|
||||
webPlayer, activePlayer, overlay, overlayTitle, overlaySubTitle,
|
||||
mines, bombSelected, foundMines, red, blue, cells, gridReady, connectionLost, gameUuid,
|
||||
// Setters needed by useServerComm
|
||||
/** Setters needed by useServerComm */
|
||||
setCells, setGridReady, setGameUuid,
|
||||
// Refs (needed by useServerComm for async-safe reads)
|
||||
/** Refs (needed by useServerComm for async-safe reads) */
|
||||
webPlayerRef, activePlayerRef, bombSelectedRef, connectionLostRef, lastClickedRef, endRef,
|
||||
// Sync helpers
|
||||
/** Sync helpers */
|
||||
syncWebPlayer, syncActivePlayer, syncBombSelected, syncConnLost, syncRed, syncBlue,
|
||||
// Game logic called by useServerComm
|
||||
/** Game logic called by useServerComm */
|
||||
showOverlay, hideOverlay,
|
||||
applyRevealedCell, applyStep,
|
||||
makeGameEndIfItEnds, resignProcess,
|
||||
// UI action
|
||||
/** UI action */
|
||||
onBombToggle,
|
||||
/** Sounds */
|
||||
sounds,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -30,7 +30,7 @@ const useGameDataProvider = gameAssoc => {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ gameAssoc }),
|
||||
}),
|
||||
}).then(r => r.json()),
|
||||
});
|
||||
|
||||
const joinMutation = useMutation({
|
||||
@@ -112,3 +112,21 @@ export const useLobbyDataProvider = () => {
|
||||
};
|
||||
|
||||
export default useGameDataProvider;
|
||||
|
||||
/**
|
||||
* Profile Data Provider Hook
|
||||
* Centralized API communication layer for profile-related mutations
|
||||
*/
|
||||
export const useProfileDataProvider = () => {
|
||||
const uploadAvatarMutation = useMutation({
|
||||
mutationFn: ({ uploadUrl, file }) => {
|
||||
const fd = new FormData();
|
||||
fd.append('avatar', file);
|
||||
return fetch(uploadUrl, { method: 'POST', body: fd })
|
||||
.then(r => r.json())
|
||||
.then(data => { if (data.error) throw new Error(data.error); return data; });
|
||||
},
|
||||
});
|
||||
|
||||
return { uploadAvatarMutation };
|
||||
};
|
||||
|
||||
@@ -10,9 +10,8 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { useGame } from '@mine-contexts';
|
||||
import { DESC, IMAGES } from '@mine-utils';
|
||||
import useStepTimer from './useStepTimer';
|
||||
import useGameDataProvider from './useGameDataProvider';
|
||||
import { ChallengeCountdown, WaitingOverlayContent } from '@mine-components';
|
||||
import { useGameDataProvider, useStepTimer } from '@mine-hooks';
|
||||
|
||||
const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev) => {
|
||||
const {
|
||||
@@ -23,7 +22,7 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
/** Sync helpers */
|
||||
syncWebPlayer, syncActivePlayer, syncBombSelected, syncConnLost, syncRed, syncBlue,
|
||||
/** Game logic */
|
||||
showOverlay, hideOverlay, applyStep, makeGameEndIfItEnds, resignProcess,
|
||||
showOverlay, hideOverlay, applyStep, makeGameEndIfItEnds, resignProcess, sounds,
|
||||
/** Current cells snapshot (for active-check in onClick) */
|
||||
cells,
|
||||
} = useGame();
|
||||
@@ -211,8 +210,9 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
* For a truly restored game, keep the "Waiting for opponent..." overlay
|
||||
* up until we actually see a heartbeat from the other player.
|
||||
*/
|
||||
if (!isTrueRestoredRef.current || 0 !== opponentLastSeenRef.current) {
|
||||
if (!endRef.current && (!isTrueRestoredRef.current || 0 !== opponentLastSeenRef.current)) {
|
||||
hideOverlay();
|
||||
sounds.current.starting.play();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -294,7 +294,7 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
onSuccess: () => {
|
||||
showOverlay('Challenge accepted!', 'Waiting for the challenger to join...');
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -311,7 +311,7 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
/>
|
||||
) : '');
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -366,7 +366,7 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
if (me && payload.color && payload.color !== me) {
|
||||
const wasFirst = 0 === opponentLastSeenRef.current;
|
||||
opponentLastSeenRef.current = Date.now();
|
||||
if (wasFirst && isTrueRestoredRef.current) {
|
||||
if (wasFirst && isTrueRestoredRef.current && !endRef.current) {
|
||||
hideOverlay();
|
||||
}
|
||||
}
|
||||
@@ -457,7 +457,12 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
/** Open event source after showing overlay */
|
||||
openEventSource();
|
||||
} else {
|
||||
await startMutation.mutateAsync();
|
||||
const startResponse = await startMutation.mutateAsync();
|
||||
if (!startResponse?.success) {
|
||||
showOverlay('Error', 'Failed to start game. Please try again.');
|
||||
isEnvDev && console.error('Start game failed:', startResponse);
|
||||
return;
|
||||
}
|
||||
openEventSource();
|
||||
wInit();
|
||||
}
|
||||
@@ -467,6 +472,7 @@ const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev
|
||||
startHeartbeat();
|
||||
} catch (e) {
|
||||
isEnvDev && console.error('Connection error', e);
|
||||
showOverlay('Error', 'Connection failed. Please try again.');
|
||||
setTimeout(() => window.location.reload(), 500);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -10,24 +10,18 @@
|
||||
import { useRef } from 'react';
|
||||
|
||||
const useStepTimer = () => {
|
||||
// Record when the current turn started (timestamp)
|
||||
const turnStartTimeRef = useRef(null);
|
||||
// Flag to track if we've already recorded a turn start
|
||||
const turnStartedRef = useRef(false);
|
||||
|
||||
const getStepElapsed = (currentActivePlayer, isGameRunning) => {
|
||||
// If game not running, return 0
|
||||
if (!isGameRunning) return 0;
|
||||
|
||||
// Only initialize the turn timer ONCE per call to getStepElapsed
|
||||
// This prevents resetting on multiple calls
|
||||
if (!turnStartedRef.current) {
|
||||
turnStartTimeRef.current = Date.now();
|
||||
turnStartedRef.current = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// After initialization, just calculate elapsed time
|
||||
if (turnStartTimeRef.current) {
|
||||
return Math.floor((Date.now() - turnStartTimeRef.current) / 1000);
|
||||
}
|
||||
@@ -40,7 +34,6 @@ const useStepTimer = () => {
|
||||
turnStartedRef.current = false;
|
||||
};
|
||||
|
||||
// Call this when we know a turn has actually changed (from server response)
|
||||
const startNewTurn = () => {
|
||||
turnStartTimeRef.current = Date.now();
|
||||
turnStartedRef.current = true;
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import PasskeyManager from './components/PasskeyManager';
|
||||
import PasskeyLogin from './components/PasskeyLogin';
|
||||
import { PasskeyLogin, PasskeyManager } from '@global-components';
|
||||
|
||||
const passkeyManagerRoot = document.getElementById('passkey-manager-root');
|
||||
|
||||
|
||||
+45
-8
@@ -1,18 +1,30 @@
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import ProfileCharts from './components/ProfileCharts';
|
||||
import BattleDialog from './components/BattleDialog';
|
||||
import AvatarUpload from './components/AvatarUpload';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { AvatarUpload, BattleDialog, ProfileCharts } from '@global-components';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const avatarRoot = document.getElementById('profile-avatar-root');
|
||||
if (avatarRoot) {
|
||||
const { uploadUrl, thumbUrl, initials } = avatarRoot.dataset;
|
||||
createRoot(avatarRoot).render(
|
||||
<AvatarUpload
|
||||
uploadUrl={uploadUrl}
|
||||
initialThumbUrl={thumbUrl || null}
|
||||
initials={initials}
|
||||
/>,
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AvatarUpload
|
||||
uploadUrl={uploadUrl}
|
||||
initialThumbUrl={thumbUrl || null}
|
||||
initials={initials}
|
||||
/>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,3 +41,28 @@ if (battleRoot) {
|
||||
<BattleDialog games={JSON.parse(battleRoot.dataset.games)} />,
|
||||
);
|
||||
}
|
||||
|
||||
const list = document.querySelector('.profile-games');
|
||||
const loadMoreBtn = document.querySelector('[data-load-more]');
|
||||
if (list && loadMoreBtn) {
|
||||
const batchSize = parseInt(list.dataset.batchSize, 10) || 5;
|
||||
loadMoreBtn.addEventListener('click', () => {
|
||||
const hidden = list.querySelectorAll('.profile-game--hidden');
|
||||
Array.from(hidden).slice(0, batchSize).forEach(el => el.classList.remove('profile-game--hidden'));
|
||||
if (0 === list.querySelectorAll('.profile-game--hidden').length) {
|
||||
loadMoreBtn.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const filterInput = document.querySelector('[data-filter]');
|
||||
if (list && filterInput) {
|
||||
filterInput.addEventListener('input', () => {
|
||||
const term = filterInput.value.trim().toLowerCase();
|
||||
list.classList.toggle('is-filtering', 0 < term.length);
|
||||
list.querySelectorAll('.profile-game').forEach(card => {
|
||||
const opp = card.querySelector('.profile-game__opponent')?.textContent.trim().toLowerCase() ?? '';
|
||||
card.classList.toggle('profile-game--filtered-out', 0 < term.length && !opp.includes(term));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/** Formats a duration in seconds as MM:SS. */
|
||||
export const formatTime = seconds => {
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = seconds % 60;
|
||||
return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats the difference between two 'YYYY-MM-DD HH:mm' strings as a
|
||||
* human-readable duration (e.g. "1h 4m 23s", "4m 23s", "23s").
|
||||
* Returns null when the inputs are missing or the diff is not positive.
|
||||
*/
|
||||
export const formatDuration = (from, to) => {
|
||||
if (!from || !to) return null;
|
||||
const diffMs = new Date(to.replace(' ', 'T')) - new Date(from.replace(' ', 'T'));
|
||||
if (isNaN(diffMs) || 0 >= diffMs) return null;
|
||||
const totalSec = Math.floor(diffMs / 1000);
|
||||
const h = Math.floor(totalSec / 3600);
|
||||
const m = Math.floor((totalSec % 3600) / 60);
|
||||
const s = totalSec % 60;
|
||||
if (0 < h) return `${h}h ${m}m ${s}s`;
|
||||
if (0 < m) return `${m}m ${s}s`;
|
||||
return `${s}s`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats an ISO timestamp as a "X min ago" string (minute resolution).
|
||||
* Returns 'just now' for differences under one minute.
|
||||
*/
|
||||
export const formatSince = isoStr => {
|
||||
const diff = Math.floor((Date.now() - new Date(isoStr)) / 60000);
|
||||
if (1 > diff) return 'just now';
|
||||
if (1 === diff) return '1 min ago';
|
||||
return `${diff} min ago`;
|
||||
};
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
CONTAINER_ID=$(docker compose ps -q minio)
|
||||
if [ -z "$CONTAINER_ID" ]; then
|
||||
echo "Error: The MineSeeker MinIO service is not running."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VOLUME_NAME=$(docker inspect "$CONTAINER_ID" --format '{{range .Mounts}}{{if eq .Destination "/data"}}{{.Name}}{{end}}{{end}}')
|
||||
if [ -z "$VOLUME_NAME" ]; then
|
||||
echo "Error: Could not determine the MinIO data volume."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BACKUP_DIR=${1:-"$PROJECT_DIR/backups"}
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
BACKUP_DIR=$(CDPATH= cd -- "$BACKUP_DIR" && pwd)
|
||||
TIMESTAMP=$(date +%Y-%m-%d.%H%M%S)
|
||||
BACKUP_FILE="mineseeker-minio.${TIMESTAMP}.tar.gz"
|
||||
BACKUP_PATH="$BACKUP_DIR/$BACKUP_FILE"
|
||||
|
||||
umask 077
|
||||
|
||||
restart_minio() {
|
||||
docker compose start minio || echo "Warning: Failed to restart MinIO."
|
||||
}
|
||||
trap restart_minio EXIT
|
||||
|
||||
echo "Stopping MinIO for a consistent backup..."
|
||||
docker compose stop minio
|
||||
|
||||
echo "Creating MinIO backup: $BACKUP_PATH"
|
||||
docker run --rm \
|
||||
--volume "$VOLUME_NAME:/data:ro" \
|
||||
--volume "$BACKUP_DIR:/backup" \
|
||||
busybox tar czf "/backup/$BACKUP_FILE" -C /data .
|
||||
|
||||
echo "Backup complete: $BACKUP_PATH"
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
CONTAINER_ID=$(docker compose ps -q db)
|
||||
if [ -z "$CONTAINER_ID" ]; then
|
||||
echo "Error: The MineSeeker PostgreSQL service is not running."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DB_USER=$(docker inspect "$CONTAINER_ID" --format '{{range .Config.Env}}{{println .}}{{end}}' | sed -n 's/^POSTGRES_USER=//p')
|
||||
DB_NAME=$(docker inspect "$CONTAINER_ID" --format '{{range .Config.Env}}{{println .}}{{end}}' | sed -n 's/^POSTGRES_DB=//p')
|
||||
|
||||
if [ -z "$DB_USER" ] || [ -z "$DB_NAME" ]; then
|
||||
echo "Error: Could not read PostgreSQL credentials from the db service."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BACKUP_DIR=${1:-"$PROJECT_DIR/backups"}
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
BACKUP_DIR=$(CDPATH= cd -- "$BACKUP_DIR" && pwd)
|
||||
TIMESTAMP=$(date +%Y-%m-%d.%H%M%S)
|
||||
BACKUP_FILE="mineseeker-postgres.${TIMESTAMP}.dump"
|
||||
BACKUP_PATH="$BACKUP_DIR/$BACKUP_FILE"
|
||||
TEMP_PATH="$BACKUP_PATH.tmp"
|
||||
|
||||
umask 077
|
||||
trap 'rm -f "$TEMP_PATH"' EXIT
|
||||
|
||||
echo "Creating PostgreSQL backup: $BACKUP_PATH"
|
||||
docker exec "$CONTAINER_ID" pg_dump \
|
||||
--username="$DB_USER" \
|
||||
--format=custom \
|
||||
--create \
|
||||
"$DB_NAME" > "$TEMP_PATH"
|
||||
|
||||
mv "$TEMP_PATH" "$BACKUP_PATH"
|
||||
trap - EXIT
|
||||
echo "Backup complete: $BACKUP_PATH"
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
|
||||
Executable
+61
@@ -0,0 +1,61 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: $0 <backup-file>"
|
||||
echo "Example: $0 backups/mineseeker-minio.2026-08-18.120000.tar.gz"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BACKUP_FILE=$1
|
||||
if [ ! -f "$BACKUP_FILE" ]; then
|
||||
echo "Error: Backup file not found: $BACKUP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BACKUP_DIR=$(CDPATH= cd -- "$(dirname -- "$BACKUP_FILE")" && pwd)
|
||||
BACKUP_NAME=$(basename "$BACKUP_FILE")
|
||||
|
||||
CONTAINER_ID=$(docker compose ps -q minio)
|
||||
if [ -z "$CONTAINER_ID" ]; then
|
||||
echo "Error: The MineSeeker MinIO service is not running."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VOLUME_NAME=$(docker inspect "$CONTAINER_ID" --format '{{range .Mounts}}{{if eq .Destination "/data"}}{{.Name}}{{end}}{{end}}')
|
||||
if [ -z "$VOLUME_NAME" ]; then
|
||||
echo "Error: Could not determine the MinIO data volume."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Verifying MinIO archive..."
|
||||
docker run --rm \
|
||||
--volume "$BACKUP_DIR:/backup:ro" \
|
||||
busybox tar -tzf "/backup/$BACKUP_NAME" > /dev/null
|
||||
echo "Archive OK."
|
||||
|
||||
printf 'WARNING: This erases all current MineSeeker MinIO data. Type yes to continue: '
|
||||
read -r CONFIRM
|
||||
if [ "$CONFIRM" != 'yes' ]; then
|
||||
echo "Aborted."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
restart_minio() {
|
||||
docker compose start minio || echo "Warning: Failed to restart MinIO."
|
||||
}
|
||||
trap restart_minio EXIT
|
||||
|
||||
echo "Stopping MinIO before restore..."
|
||||
docker compose stop minio
|
||||
|
||||
echo "Restoring MinIO backup: $BACKUP_FILE"
|
||||
docker run --rm \
|
||||
--volume "$VOLUME_NAME:/data" \
|
||||
--volume "$BACKUP_DIR:/backup:ro" \
|
||||
busybox sh -c 'find /data -mindepth 1 -maxdepth 1 -exec rm -rf {} + && tar xzf "/backup/$1" -C /data' sh "$BACKUP_NAME"
|
||||
|
||||
echo "Restore complete."
|
||||
Executable
+84
@@ -0,0 +1,84 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: $0 <backup-file>"
|
||||
echo "Example: $0 backups/mineseeker-postgres.2026-08-18.120000.dump"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BACKUP_FILE=$1
|
||||
if [ ! -f "$BACKUP_FILE" ]; then
|
||||
echo "Error: Backup file not found: $BACKUP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CONTAINER_ID=$(docker compose ps -q db)
|
||||
if [ -z "$CONTAINER_ID" ]; then
|
||||
echo "Error: The MineSeeker PostgreSQL service is not running."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DB_USER=$(docker inspect "$CONTAINER_ID" --format '{{range .Config.Env}}{{println .}}{{end}}' | sed -n 's/^POSTGRES_USER=//p')
|
||||
if [ -z "$DB_USER" ]; then
|
||||
echo "Error: Could not read POSTGRES_USER from the db service."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DB_NAME=$(docker inspect "$CONTAINER_ID" --format '{{range .Config.Env}}{{println .}}{{end}}' | sed -n 's/^POSTGRES_DB=//p')
|
||||
if [ -z "$DB_NAME" ]; then
|
||||
echo "Error: Could not read POSTGRES_DB from the db service."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Verifying PostgreSQL archive..."
|
||||
docker exec -i "$CONTAINER_ID" pg_restore --list < "$BACKUP_FILE" > /dev/null
|
||||
echo "Archive OK."
|
||||
|
||||
printf 'WARNING: This replaces the current MineSeeker database. Type yes to continue: '
|
||||
read -r CONFIRM
|
||||
if [ "$CONFIRM" != 'yes' ]; then
|
||||
echo "Aborted."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
APP_WAS_RUNNING=0
|
||||
SCHEDULER_WAS_RUNNING=0
|
||||
[ -n "$(docker compose ps -q app)" ] && APP_WAS_RUNNING=1
|
||||
[ -n "$(docker compose ps -q scheduler)" ] && SCHEDULER_WAS_RUNNING=1
|
||||
|
||||
restart_services() {
|
||||
[ "$APP_WAS_RUNNING" -eq 1 ] && docker compose start app || true
|
||||
[ "$SCHEDULER_WAS_RUNNING" -eq 1 ] && docker compose start scheduler || true
|
||||
}
|
||||
trap restart_services EXIT
|
||||
|
||||
echo "Stopping MineSeeker services that use PostgreSQL..."
|
||||
docker compose stop app scheduler
|
||||
|
||||
echo "Dropping configured database '$DB_NAME'..."
|
||||
docker exec "$CONTAINER_ID" dropdb \
|
||||
--username="$DB_USER" \
|
||||
--if-exists \
|
||||
--force \
|
||||
"$DB_NAME"
|
||||
|
||||
echo "Creating '$DB_NAME' owned by '$DB_USER'..."
|
||||
docker exec "$CONTAINER_ID" createdb \
|
||||
--username="$DB_USER" \
|
||||
--owner="$DB_USER" \
|
||||
--template=template0 \
|
||||
"$DB_NAME"
|
||||
|
||||
echo "Restoring PostgreSQL backup without source ownership/ACLs: $BACKUP_FILE"
|
||||
docker exec -i "$CONTAINER_ID" pg_restore \
|
||||
--username="$DB_USER" \
|
||||
--dbname="$DB_NAME" \
|
||||
--no-owner \
|
||||
--no-acl \
|
||||
--exit-on-error < "$BACKUP_FILE"
|
||||
|
||||
echo "Restore complete."
|
||||
@@ -7,30 +7,31 @@
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@fontsource/changa-one": "^5.2.8",
|
||||
"@fontsource/open-sans": "^5.2.7",
|
||||
"@fontsource/rajdhani": "^5.2.7",
|
||||
"@fortawesome/fontawesome-free": "^7.2.0",
|
||||
"@mui/material": "^9.0.0",
|
||||
"@mui/x-charts": "^9.0.2",
|
||||
"@tanstack/react-query": "^5.99.2",
|
||||
"@fontsource/changa-one": "^5.3.0",
|
||||
"@fontsource/open-sans": "^5.3.0",
|
||||
"@fontsource/rajdhani": "^5.3.0",
|
||||
"@fortawesome/fontawesome-free": "^7.3.1",
|
||||
"@mui/material": "^9.2.0",
|
||||
"@mui/x-charts": "^9.10.1",
|
||||
"@tanstack/react-query": "^5.101.4",
|
||||
"cap-widget": "^0.1.56",
|
||||
"howler": "^2.2.4",
|
||||
"lodash": "^4.18.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.5",
|
||||
"@eslint/eslintrc": "^3.3.6",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@stylistic/eslint-plugin": "5.10.0",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"eslint": "10.2.1",
|
||||
"@vitejs/plugin-react": "^6.0.4",
|
||||
"eslint": "^10.8.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "7.1.1",
|
||||
"globals": "17.5.0",
|
||||
"sass": "^1.99.0",
|
||||
"vite": "^8.0.8",
|
||||
"globals": "^17.8.0",
|
||||
"sass": "^1.102.0",
|
||||
"vite": "^8.1.5",
|
||||
"vite-plugin-symfony": "^8.2.4",
|
||||
},
|
||||
},
|
||||
@@ -38,27 +39,27 @@
|
||||
"packages": {
|
||||
"@babel/code-frame": ["@babel/code-frame@7.29.0", "http://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
|
||||
|
||||
"@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
|
||||
"@babel/compat-data": ["@babel/compat-data@7.29.0", "http://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
|
||||
|
||||
"@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
|
||||
"@babel/core": ["@babel/core@7.29.0", "http://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.29.1", "http://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
|
||||
|
||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
|
||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "http://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
|
||||
|
||||
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "http://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
|
||||
|
||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "http://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
|
||||
|
||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
|
||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "http://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "http://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "http://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "http://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
|
||||
"@babel/helpers": ["@babel/helpers@7.29.2", "http://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.29.2", "http://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
|
||||
|
||||
@@ -70,11 +71,7 @@
|
||||
|
||||
"@babel/types": ["@babel/types@7.29.0", "http://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
||||
|
||||
"@emnapi/core": ["@emnapi/core@1.9.2", "http://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.9.2", "http://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],
|
||||
|
||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "http://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
|
||||
"@base-ui/utils": ["@base-ui/utils@0.3.1", "http://registry.npmjs.org/@base-ui/utils/-/utils-0.3.1.tgz", { "dependencies": { "@babel/runtime": "^7.29.2", "@floating-ui/utils": "^0.2.11", "reselect": "^5.2.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-gFFiltORVmW/N6IILTGxizP3PBpVpysqML1ALY5Vk0mH+7faVkCknOU31goYHN5Aoek2dkjxva1XOD2Ce9WuIg=="],
|
||||
|
||||
"@emotion/babel-plugin": ["@emotion/babel-plugin@11.13.5", "http://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", { "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", "@emotion/serialize": "^1.3.3", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", "find-root": "^1.1.0", "source-map": "^0.5.7", "stylis": "4.2.0" } }, "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ=="],
|
||||
|
||||
@@ -106,27 +103,29 @@
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "http://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
|
||||
|
||||
"@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="],
|
||||
"@eslint/config-array": ["@eslint/config-array@0.23.5", "http://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="],
|
||||
|
||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.5.5", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w=="],
|
||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.7.0", "http://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw=="],
|
||||
|
||||
"@eslint/core": ["@eslint/core@1.2.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="],
|
||||
"@eslint/core": ["@eslint/core@1.2.1", "http://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="],
|
||||
|
||||
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.5", "http://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg=="],
|
||||
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.6", "http://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.3.0", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA=="],
|
||||
|
||||
"@eslint/js": ["@eslint/js@10.0.1", "", { "peerDependencies": { "eslint": "^10.0.0" }, "optionalPeers": ["eslint"] }, "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA=="],
|
||||
"@eslint/js": ["@eslint/js@10.0.1", "http://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", { "peerDependencies": { "eslint": "^10.0.0" }, "optionalPeers": ["eslint"] }, "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA=="],
|
||||
|
||||
"@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="],
|
||||
"@eslint/object-schema": ["@eslint/object-schema@3.0.5", "http://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="],
|
||||
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.1", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ=="],
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.2", "http://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A=="],
|
||||
|
||||
"@fontsource/changa-one": ["@fontsource/changa-one@5.2.8", "http://registry.npmjs.org/@fontsource/changa-one/-/changa-one-5.2.8.tgz", {}, "sha512-gagiU8sMWLs9ejh41NmrYlGdgasSYWFegz5/+22WqYdJVS9HZbaUEXj/6jj3ZKgi+dQZT0kYI+Nha2UQGbO/mA=="],
|
||||
"@floating-ui/utils": ["@floating-ui/utils@0.2.12", "http://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", {}, "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww=="],
|
||||
|
||||
"@fontsource/open-sans": ["@fontsource/open-sans@5.2.7", "http://registry.npmjs.org/@fontsource/open-sans/-/open-sans-5.2.7.tgz", {}, "sha512-8yfgDYjE5O0vmTPdrcjV35y4yMnctsokmi9gN49Gcsr0sjzkMkR97AnKDe6OqZh2SFkYlR28fxOvi21bYEgMSw=="],
|
||||
"@fontsource/changa-one": ["@fontsource/changa-one@5.3.0", "http://registry.npmjs.org/@fontsource/changa-one/-/changa-one-5.3.0.tgz", {}, "sha512-ACriykYOe12aupip0QfAaNo9/XNEt0WD1U1CiL/ZUGnrzIXFizJEkUzjnXWg+M4GXkY4n0o/W6TUtcNqMn0GrA=="],
|
||||
|
||||
"@fontsource/rajdhani": ["@fontsource/rajdhani@5.2.7", "http://registry.npmjs.org/@fontsource/rajdhani/-/rajdhani-5.2.7.tgz", {}, "sha512-7Gy10U688fCdeFfYKebUF2TZotdgH/ghKyMsseXPmB60lpaUHC8aoCSJl5/OpZ+KHKSU2TqBfKfteVkcIXxTAQ=="],
|
||||
"@fontsource/open-sans": ["@fontsource/open-sans@5.3.0", "http://registry.npmjs.org/@fontsource/open-sans/-/open-sans-5.3.0.tgz", {}, "sha512-V1bdMlGNyZrJwzrdusshMOw2YR0aqYDL7kOo9dkxUEKl8dFv66A9HLckOOXl572sG/7ohhDQrCrTFGSn9LZcSw=="],
|
||||
|
||||
"@fortawesome/fontawesome-free": ["@fortawesome/fontawesome-free@7.2.0", "http://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.2.0.tgz", {}, "sha512-3DguDv/oUE+7vjMeTSOjCSG+KeawgVQOHrKRnvUuqYh1mfArrh7s+s8hXW3e4RerBA1+Wh+hBqf8sJNpqNrBWg=="],
|
||||
"@fontsource/rajdhani": ["@fontsource/rajdhani@5.3.0", "http://registry.npmjs.org/@fontsource/rajdhani/-/rajdhani-5.3.0.tgz", {}, "sha512-oBJgSIibWJvVBKseme1JczqPLoDHN97LHti4uh0LjfcJqQoVmnQQlRKIpEjTJV559rmVQ6qbVZZwKwk2wkBTtw=="],
|
||||
|
||||
"@fortawesome/fontawesome-free": ["@fortawesome/fontawesome-free@7.3.1", "http://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.3.1.tgz", {}, "sha512-wmglKKPDIkgV3aWlZzWECCPoGIkYCulzBwxG9+w7rc5BGapZ6cPMpoPOT8k36J0Ni7PPX6c/rsoMWfS4d1MUMg=="],
|
||||
|
||||
"@humanfs/core": ["@humanfs/core@0.19.1", "http://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
||||
|
||||
@@ -138,7 +137,7 @@
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "http://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "http://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "http://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
@@ -146,29 +145,27 @@
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "http://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@mui/core-downloads-tracker": ["@mui/core-downloads-tracker@9.0.0", "http://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-9.0.0.tgz", {}, "sha512-uwQNGkhv0lf7ufxw6QXev77BW6pWbW+7uxYjU5+rfp4lBkFtMEgJCsarTM3Tn+i0lGx6+Ol2u88JdGXr0GDskA=="],
|
||||
"@mui/core-downloads-tracker": ["@mui/core-downloads-tracker@9.3.1", "", {}, "sha512-IAyAFNQbT7hysJ9HXphiOmWJF7G1OglzHanqCgvQgH9LA2ydxtmaTBDbcBqw6euZesyShiwvpvbnYO1GY1AyXQ=="],
|
||||
|
||||
"@mui/material": ["@mui/material@9.0.0", "http://registry.npmjs.org/@mui/material/-/material-9.0.0.tgz", { "dependencies": { "@babel/runtime": "^7.29.2", "@mui/core-downloads-tracker": "^9.0.0", "@mui/system": "^9.0.0", "@mui/types": "^9.0.0", "@mui/utils": "^9.0.0", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", "csstype": "^3.2.3", "prop-types": "^15.8.1", "react-is": "^19.2.4", "react-transition-group": "^4.4.5" }, "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", "@mui/material-pigment-css": "^9.0.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/react", "@emotion/styled", "@mui/material-pigment-css", "@types/react"] }, "sha512-+VP/oQCDhDR87NQQgXnNBG8dwy6GNuQLnenS1pZvkbn2dKFSxRSRMybTpH9xUxXP+316mlYDy5CSbYtusnCWtw=="],
|
||||
"@mui/material": ["@mui/material@9.3.1", "", { "dependencies": { "@babel/runtime": "^7.29.7", "@mui/core-downloads-tracker": "^9.3.1", "@mui/system": "^9.3.0", "@mui/types": "^9.3.0", "@mui/utils": "^9.3.0", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", "csstype": "^3.2.3", "prop-types": "^15.8.1", "react-is": "^19.2.8", "react-transition-group": "^4.4.5" }, "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", "@mui/material-pigment-css": "^9.3.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/react", "@emotion/styled", "@mui/material-pigment-css", "@types/react"] }, "sha512-NahAEGIXqS1K0bA4th1jeFxBguS59NOcLbMA0vU+fSaPWKjtwGBGGHeTwlc9PSmzMjOZKceeFWESB8fVHr31hA=="],
|
||||
|
||||
"@mui/private-theming": ["@mui/private-theming@9.0.0", "http://registry.npmjs.org/@mui/private-theming/-/private-theming-9.0.0.tgz", { "dependencies": { "@babel/runtime": "^7.29.2", "@mui/utils": "^9.0.0", "prop-types": "^15.8.1" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-JtuZoaiCqwD6vjgYu6Xp3T7DZkrxJlgtDz5yESzhI34fEX5hHMh2VJUbuL9UOg8xrfIFMrq6dcYoH/7Zi4G0RA=="],
|
||||
"@mui/private-theming": ["@mui/private-theming@9.3.0", "", { "dependencies": { "@babel/runtime": "^7.29.7", "@mui/utils": "^9.3.0", "prop-types": "^15.8.1" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-ERvqk5pejf9aRnQcDILSWGtFmsEMiVxlQ4+xsVCjsEvmK0fV9BiVP/cQwAF5dwyDFbve4lTrlcgeFEecVzTNiA=="],
|
||||
|
||||
"@mui/styled-engine": ["@mui/styled-engine@9.0.0", "http://registry.npmjs.org/@mui/styled-engine/-/styled-engine-9.0.0.tgz", { "dependencies": { "@babel/runtime": "^7.29.2", "@emotion/cache": "^11.14.0", "@emotion/serialize": "^1.3.3", "@emotion/sheet": "^1.4.0", "csstype": "^3.2.3", "prop-types": "^15.8.1" }, "peerDependencies": { "@emotion/react": "^11.4.1", "@emotion/styled": "^11.3.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/react", "@emotion/styled"] }, "sha512-9RLGdX4Jg0aQPRuvqh/OLzYSPlgd5zyEw5/1HIRfdavSiOd03WtUaGZH9/w1RoTYuRKwpgy0hpIFaMHIqPVIWg=="],
|
||||
"@mui/styled-engine": ["@mui/styled-engine@9.3.0", "", { "dependencies": { "@babel/runtime": "^7.29.7", "@emotion/cache": "^11.14.0", "@emotion/serialize": "^1.3.3", "@emotion/sheet": "^1.4.0", "csstype": "^3.2.3", "prop-types": "^15.8.1" }, "peerDependencies": { "@emotion/react": "^11.4.1", "@emotion/styled": "^11.3.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/react", "@emotion/styled"] }, "sha512-x9+KYxhjoHYZ4nioxdKnvQWdw0RScbhoZfQ4tv3Db742683U3wlYSp6zV6Us78dMFnKnyTrPTkQKyutp14gnKA=="],
|
||||
|
||||
"@mui/system": ["@mui/system@9.0.0", "http://registry.npmjs.org/@mui/system/-/system-9.0.0.tgz", { "dependencies": { "@babel/runtime": "^7.29.2", "@mui/private-theming": "^9.0.0", "@mui/styled-engine": "^9.0.0", "@mui/types": "^9.0.0", "@mui/utils": "^9.0.0", "clsx": "^2.1.1", "csstype": "^3.2.3", "prop-types": "^15.8.1" }, "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/react", "@emotion/styled", "@types/react"] }, "sha512-YnC5Zg6j04IxiLc/boAKs0464jfZlLFVa7mf5E8lF0XOtZVUvG6R6gJK50lgUYdaaLdyLfxF6xR7LaPuEpeT/g=="],
|
||||
"@mui/system": ["@mui/system@9.3.0", "", { "dependencies": { "@babel/runtime": "^7.29.7", "@mui/private-theming": "^9.3.0", "@mui/styled-engine": "^9.3.0", "@mui/types": "^9.3.0", "@mui/utils": "^9.3.0", "clsx": "^2.1.1", "csstype": "^3.2.3", "prop-types": "^15.8.1" }, "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/react", "@emotion/styled", "@types/react"] }, "sha512-0l4LqHJxZj65xSrioniGsxm7VNoGXonPo203oZjhBUvIDPeBqRTb7Mqc45Qxs6sO6WGR7WE/9cJ6lb4lkvHkjg=="],
|
||||
|
||||
"@mui/types": ["@mui/types@9.0.0", "http://registry.npmjs.org/@mui/types/-/types-9.0.0.tgz", { "dependencies": { "@babel/runtime": "^7.29.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-i1cuFCAWN44b3AJWO7mh7tuh1sqbQSeVr/94oG0TX5uXivac8XalgE4/6fQZcmGZigzbQ35IXxj/4jLpRIBYZg=="],
|
||||
"@mui/types": ["@mui/types@9.3.0", "", { "dependencies": { "@babel/runtime": "^7.29.7" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-2JSxyfpEFWNUB2vKs/T1BvkfyNisMHWph8bLMj8T0uHwmLl/0qfAwQkfwMT6kxLXN9uIum9AEbECXU8er3amIg=="],
|
||||
|
||||
"@mui/utils": ["@mui/utils@9.0.0", "http://registry.npmjs.org/@mui/utils/-/utils-9.0.0.tgz", { "dependencies": { "@babel/runtime": "^7.29.2", "@mui/types": "^9.0.0", "@types/prop-types": "^15.7.15", "clsx": "^2.1.1", "prop-types": "^15.8.1", "react-is": "^19.2.4" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-bQcqyg/gjULUqTuyUjSAFr6LQGLvtkNtDbJerAtoUn9kGZ0hg5QJiN1PLHMLbeFpe3te1831uq7GFl2ITokGdg=="],
|
||||
"@mui/utils": ["@mui/utils@9.3.0", "", { "dependencies": { "@babel/runtime": "^7.29.7", "@mui/types": "^9.3.0", "@types/prop-types": "^15.7.15", "clsx": "^2.1.1", "prop-types": "^15.8.1", "react-is": "^19.2.8" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-2HZdHwWJ6eB+7lVGSOHsByGw8jeRulT4g0NZ608Wb8Q57DE2jbNqrWPFuJsvkQQiBiTmlpvQL3i+/62zsiPrkw=="],
|
||||
|
||||
"@mui/x-charts": ["@mui/x-charts@9.0.2", "http://registry.npmjs.org/@mui/x-charts/-/x-charts-9.0.2.tgz", { "dependencies": { "@babel/runtime": "^7.28.6", "@mui/utils": "9.0.0", "@mui/x-charts-vendor": "^9.0.0", "@mui/x-internal-gestures": "^9.0.2", "@mui/x-internals": "^9.0.0", "bezier-easing": "^2.1.0", "clsx": "^2.1.1", "prop-types": "^15.8.1", "reselect": "^5.1.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", "@mui/material": "^7.3.0 || ^9.0.0", "@mui/system": "^7.3.0 || ^9.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/react", "@emotion/styled"] }, "sha512-bKgjGD+uJbDN/g7tMjVmlNdm+iM4UkCJoYruQmgpQ0l+cip8Kn4kmn1iD//rZ35an+LdWaUZ4MHvMzV76D6EJw=="],
|
||||
"@mui/x-charts": ["@mui/x-charts@9.11.1", "", { "dependencies": { "@babel/runtime": "^7.29.7", "@mui/utils": "^9.3.0", "@mui/x-charts-vendor": "^9.11.1", "@mui/x-internal-gestures": "^9.10.0", "@mui/x-internals": "^9.11.0", "bezier-easing": "^3.0.1", "clsx": "^2.1.1", "prop-types": "^15.8.1", "reselect": "^5.2.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", "@mui/material": "^7.3.0 || ^9.0.0", "@mui/system": "^7.3.0 || ^9.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/react", "@emotion/styled"] }, "sha512-5Z6fzVm0NvD4auoyGZopp1wwg1mMVDXjbPEi4jahdxn14EzIAB99kKw4NUkKtmSaZJ6DiBU7zJXQAec1IZTaLA=="],
|
||||
|
||||
"@mui/x-charts-vendor": ["@mui/x-charts-vendor@9.0.0", "http://registry.npmjs.org/@mui/x-charts-vendor/-/x-charts-vendor-9.0.0.tgz", { "dependencies": { "@babel/runtime": "^7.28.6", "@types/d3-array": "^3.2.2", "@types/d3-color": "^3.1.3", "@types/d3-format": "^3.0.4", "@types/d3-interpolate": "^3.0.4", "@types/d3-path": "^3.1.1", "@types/d3-scale": "^4.0.9", "@types/d3-shape": "^3.1.8", "@types/d3-time": "^3.0.4", "@types/d3-time-format": "^4.0.3", "@types/d3-timer": "^3.0.2", "d3-array": "^3.2.4", "d3-color": "^3.1.0", "d3-format": "^3.1.2", "d3-interpolate": "^3.0.1", "d3-path": "^3.1.0", "d3-scale": "^4.0.2", "d3-shape": "^3.2.0", "d3-time": "^3.1.0", "d3-time-format": "^4.1.0", "d3-timer": "^3.0.1", "flatqueue": "^3.0.0", "internmap": "^2.0.3" } }, "sha512-Do91i+fZiNj/4LN5oaGpJoutolzDBDwdfw6tHrx2LKXDMCRlaImCfreLbdbkk7dFsi9fuIP7hWiMV4vDJKPJTA=="],
|
||||
"@mui/x-charts-vendor": ["@mui/x-charts-vendor@9.11.1", "", { "dependencies": { "@babel/runtime": "^7.29.7", "@types/d3-array": "^3.2.2", "@types/d3-color": "^3.1.3", "@types/d3-format": "^3.0.4", "@types/d3-geo": "^3.1.0", "@types/d3-interpolate": "^3.0.4", "@types/d3-path": "^3.1.1", "@types/d3-scale": "^4.0.9", "@types/d3-shape": "^3.1.8", "@types/d3-time": "^3.0.4", "@types/d3-time-format": "^4.0.3", "@types/d3-timer": "^3.0.2", "d3-array": "^3.2.4", "d3-color": "^3.1.0", "d3-format": "^3.1.2", "d3-geo": "^3.1.1", "d3-interpolate": "^3.0.1", "d3-path": "^3.1.0", "d3-scale": "^4.0.2", "d3-shape": "^3.2.0", "d3-time": "^3.1.0", "d3-time-format": "^4.1.0", "d3-timer": "^3.0.1", "flatqueue": "^3.1.0", "internmap": "^2.0.3" } }, "sha512-Q7cuPaKZLLsyvtEiP+ENG7xLlPatXyNQ7u5WkbAcAIZwyMK8MJw4MPgJiyWyfy9eVQO/IkRLIupabvu8cA2jPA=="],
|
||||
|
||||
"@mui/x-internal-gestures": ["@mui/x-internal-gestures@9.0.2", "http://registry.npmjs.org/@mui/x-internal-gestures/-/x-internal-gestures-9.0.2.tgz", { "dependencies": { "@babel/runtime": "^7.28.6" } }, "sha512-xCp99a7cSb7iH1bj4G524ooMOFe92H8m/rONCUiKyj7LvV1YUGzTfHgJysQgDCZJqHYaW7YAGLvwMUyEMZVzqQ=="],
|
||||
"@mui/x-internal-gestures": ["@mui/x-internal-gestures@9.10.0", "http://registry.npmjs.org/@mui/x-internal-gestures/-/x-internal-gestures-9.10.0.tgz", { "dependencies": { "@babel/runtime": "^7.29.7", "@base-ui/utils": "^0.3.1" } }, "sha512-Fgmh+vJlOjLst238/J5SlEOZLdnSe0yWjrYctYVWJr3pKbtmYSbDkjYvWjG97PeDvq5Wa1+W1qagiBoJgkUIng=="],
|
||||
|
||||
"@mui/x-internals": ["@mui/x-internals@9.0.0", "http://registry.npmjs.org/@mui/x-internals/-/x-internals-9.0.0.tgz", { "dependencies": { "@babel/runtime": "^7.28.6", "@mui/utils": "9.0.0", "reselect": "^5.1.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-E/4rdg69JjhyybpPGypCjAKSKLLnSdCFM+O6P/nkUg47+qt3uftxQEhjQO53rcn6ahHl6du/uNZ9BLgeY6kYxQ=="],
|
||||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.3", "http://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ=="],
|
||||
"@mui/x-internals": ["@mui/x-internals@9.11.0", "", { "dependencies": { "@babel/runtime": "^7.29.7", "@base-ui/utils": "^0.3.1", "@mui/utils": "^9.3.0", "reselect": "^5.2.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-JjKe9k1+gVWNPwMTZLNSc92eVmoZqP5Xq3Ui6mJkstotUrbWIZC0o9+4AfTR1lYqWwnKmqm+VtLLVCa7MEBXWQ=="],
|
||||
|
||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "http://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||
|
||||
@@ -176,7 +173,7 @@
|
||||
|
||||
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "http://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
||||
|
||||
"@oxc-project/types": ["@oxc-project/types@0.124.0", "http://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", {}, "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg=="],
|
||||
"@oxc-project/types": ["@oxc-project/types@0.144.0", "", {}, "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg=="],
|
||||
|
||||
"@parcel/watcher": ["@parcel/watcher@2.5.6", "http://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.3" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.6", "@parcel/watcher-darwin-arm64": "2.5.6", "@parcel/watcher-darwin-x64": "2.5.6", "@parcel/watcher-freebsd-x64": "2.5.6", "@parcel/watcher-linux-arm-glibc": "2.5.6", "@parcel/watcher-linux-arm-musl": "2.5.6", "@parcel/watcher-linux-arm64-glibc": "2.5.6", "@parcel/watcher-linux-arm64-musl": "2.5.6", "@parcel/watcher-linux-x64-glibc": "2.5.6", "@parcel/watcher-linux-x64-musl": "2.5.6", "@parcel/watcher-win32-arm64": "2.5.6", "@parcel/watcher-win32-ia32": "2.5.6", "@parcel/watcher-win32-x64": "2.5.6" } }, "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ=="],
|
||||
|
||||
@@ -210,45 +207,41 @@
|
||||
|
||||
"@popperjs/core": ["@popperjs/core@2.11.8", "http://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", {}, "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="],
|
||||
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.15", "http://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", { "os": "android", "cpu": "arm64" }, "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA=="],
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.4", "", { "os": "android", "cpu": "arm64" }, "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA=="],
|
||||
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.15", "http://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg=="],
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ=="],
|
||||
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.15", "http://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw=="],
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg=="],
|
||||
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.15", "http://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw=="],
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A=="],
|
||||
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15", "http://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", { "os": "linux", "cpu": "arm" }, "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA=="],
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15", "http://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w=="],
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.15", "http://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ=="],
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w=="],
|
||||
|
||||
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15", "http://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ=="],
|
||||
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ=="],
|
||||
|
||||
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15", "http://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ=="],
|
||||
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.15", "http://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", { "os": "linux", "cpu": "x64" }, "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA=="],
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.15", "http://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", { "os": "linux", "cpu": "x64" }, "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw=="],
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ=="],
|
||||
|
||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.15", "http://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", { "os": "none", "cpu": "arm64" }, "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg=="],
|
||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.4", "", { "os": "none", "cpu": "arm64" }, "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg=="],
|
||||
|
||||
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.15", "http://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", { "dependencies": { "@emnapi/core": "1.9.2", "@emnapi/runtime": "1.9.2", "@napi-rs/wasm-runtime": "^1.1.3" }, "cpu": "none" }, "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q=="],
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw=="],
|
||||
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15", "http://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA=="],
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ=="],
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.15", "http://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", { "os": "win32", "cpu": "x64" }, "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g=="],
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "http://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.7", "http://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", {}, "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA=="],
|
||||
"@stylistic/eslint-plugin": ["@stylistic/eslint-plugin@5.10.0", "http://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/types": "^8.56.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "estraverse": "^5.3.0", "picomatch": "^4.0.3" }, "peerDependencies": { "eslint": "^9.0.0 || ^10.0.0" } }, "sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ=="],
|
||||
|
||||
"@stylistic/eslint-plugin": ["@stylistic/eslint-plugin@5.10.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/types": "^8.56.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "estraverse": "^5.3.0", "picomatch": "^4.0.3" }, "peerDependencies": { "eslint": "^9.0.0 || ^10.0.0" } }, "sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ=="],
|
||||
"@tanstack/query-core": ["@tanstack/query-core@5.101.4", "http://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.4.tgz", {}, "sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw=="],
|
||||
|
||||
"@tanstack/query-core": ["@tanstack/query-core@5.99.2", "http://registry.npmjs.org/@tanstack/query-core/-/query-core-5.99.2.tgz", {}, "sha512-1HunU0bXVsR1ZJMZbcOPE6VtaBJxsW809RE9xPe4Gz7MlB0GWwQvuTPhMoEmQ/hIzFKJ/DWAuttIe7BOaWx0tA=="],
|
||||
|
||||
"@tanstack/react-query": ["@tanstack/react-query@5.99.2", "http://registry.npmjs.org/@tanstack/react-query/-/react-query-5.99.2.tgz", { "dependencies": { "@tanstack/query-core": "5.99.2" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-vM91UEe45QUS9ED6OklsVL15i8qKcRqNwpWzPTVWvRPRSEgDudDgHpvyTjcdlwHcrKNa80T+xXYcchT2noPnZA=="],
|
||||
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "http://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
"@tanstack/react-query": ["@tanstack/react-query@5.101.4", "http://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.4.tgz", { "dependencies": { "@tanstack/query-core": "5.101.4" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA=="],
|
||||
|
||||
"@types/d3-array": ["@types/d3-array@3.2.2", "http://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
|
||||
|
||||
@@ -256,6 +249,8 @@
|
||||
|
||||
"@types/d3-format": ["@types/d3-format@3.0.4", "http://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", {}, "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g=="],
|
||||
|
||||
"@types/d3-geo": ["@types/d3-geo@3.1.0", "http://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", { "dependencies": { "@types/geojson": "*" } }, "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ=="],
|
||||
|
||||
"@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "http://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="],
|
||||
|
||||
"@types/d3-path": ["@types/d3-path@3.1.1", "http://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="],
|
||||
@@ -270,10 +265,12 @@
|
||||
|
||||
"@types/d3-timer": ["@types/d3-timer@3.0.2", "http://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="],
|
||||
|
||||
"@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="],
|
||||
"@types/esrecurse": ["@types/esrecurse@4.3.1", "http://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "http://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/geojson": ["@types/geojson@7946.0.16", "http://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "http://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
|
||||
"@types/parse-json": ["@types/parse-json@4.0.2", "http://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
|
||||
@@ -286,7 +283,7 @@
|
||||
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.58.1", "http://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.1.tgz", {}, "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw=="],
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "http://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="],
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.5", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA=="],
|
||||
|
||||
"acorn": ["acorn@8.16.0", "http://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
||||
|
||||
@@ -318,15 +315,15 @@
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.0", "http://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", {}, "sha512-9Y0g0Q8rmSt+H33DfKv7FOc3v+iRI+o1lbzt8jGcIosYW37IIW/2XVYq5NPdmaD5NQ59Nk26Kl/vZbwW9Fr8vg=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.20", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ=="],
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.20", "http://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.20.tgz", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ=="],
|
||||
|
||||
"bezier-easing": ["bezier-easing@2.1.0", "http://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz", {}, "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig=="],
|
||||
"bezier-easing": ["bezier-easing@3.0.1", "http://registry.npmjs.org/bezier-easing/-/bezier-easing-3.0.1.tgz", {}, "sha512-vvvg10Anq7py0+KkIqlwJpUVdde9Z8kSzeUzAkQNYAJdA3mZnLG9r9ExdwtjcASCYER6UkNHdBNHHMnVimnU7A=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@1.1.11", "http://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "http://registry.npmjs.org/braces/-/braces-3.0.3.tgz", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
|
||||
"browserslist": ["browserslist@4.28.2", "http://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
|
||||
|
||||
"call-bind": ["call-bind@1.0.9", "http://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", { "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" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="],
|
||||
|
||||
@@ -336,15 +333,17 @@
|
||||
|
||||
"callsites": ["callsites@3.1.0", "http://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001788", "", {}, "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ=="],
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001788", "http://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz", {}, "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ=="],
|
||||
|
||||
"chokidar": ["chokidar@4.0.3", "http://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||
"cap-widget": ["cap-widget@0.1.57", "", {}, "sha512-SyIK5XgYIFSDgH683M9KpfBmDSDB0ZfPZQBxL6MggNrjjnbIQYpbsNpj0S7PC/wXp7oJgAuBD2vtJnN9jJghBg=="],
|
||||
|
||||
"chokidar": ["chokidar@5.0.0", "http://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
||||
|
||||
"clsx": ["clsx@2.1.1", "http://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "http://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "http://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"cosmiconfig": ["cosmiconfig@7.1.0", "http://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="],
|
||||
|
||||
@@ -358,6 +357,8 @@
|
||||
|
||||
"d3-format": ["d3-format@3.1.2", "http://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="],
|
||||
|
||||
"d3-geo": ["d3-geo@3.1.1", "http://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="],
|
||||
|
||||
"d3-interpolate": ["d3-interpolate@3.0.1", "http://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="],
|
||||
|
||||
"d3-path": ["d3-path@3.1.0", "http://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="],
|
||||
@@ -394,7 +395,7 @@
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "http://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.340", "", {}, "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA=="],
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.340", "http://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.340.tgz", {}, "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA=="],
|
||||
|
||||
"error-ex": ["error-ex@1.3.4", "http://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
|
||||
|
||||
@@ -414,17 +415,17 @@
|
||||
|
||||
"es-to-primitive": ["es-to-primitive@1.3.0", "http://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
"escalade": ["escalade@3.2.0", "http://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "http://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"eslint": ["eslint@10.2.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.5.5", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q=="],
|
||||
"eslint": ["eslint@10.8.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ=="],
|
||||
|
||||
"eslint-plugin-react": ["eslint-plugin-react@7.37.5", "http://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", { "dependencies": { "array-includes": "^3.1.8", "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", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "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" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA=="],
|
||||
|
||||
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.1.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g=="],
|
||||
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.1.1", "http://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g=="],
|
||||
|
||||
"eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="],
|
||||
"eslint-scope": ["eslint-scope@9.1.2", "http://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="],
|
||||
|
||||
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "http://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||
|
||||
@@ -460,7 +461,7 @@
|
||||
|
||||
"flat-cache": ["flat-cache@4.0.1", "http://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
|
||||
|
||||
"flatqueue": ["flatqueue@3.0.0", "http://registry.npmjs.org/flatqueue/-/flatqueue-3.0.0.tgz", {}, "sha512-y1deYaVt+lIc/d2uIcWDNd0CrdQTO5xoCjeFdhX0kSXvm2Acm0o+3bAOiYklTEoRyzwio3sv3/IiBZdusbAe2Q=="],
|
||||
"flatqueue": ["flatqueue@3.1.0", "", {}, "sha512-Ia4qIYrrsEqIRx3c3XhkT+QDLQuUV5ovsr6ah1rIgKT5wclhoGK3lAMS1bWRAWxlx7wtlTBpV7QXB5d9fOSRxA=="],
|
||||
|
||||
"flatted": ["flatted@3.4.2", "http://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="],
|
||||
|
||||
@@ -476,7 +477,7 @@
|
||||
|
||||
"generator-function": ["generator-function@2.0.1", "http://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="],
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
"gensync": ["gensync@1.0.0-beta.2", "http://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.3.0", "http://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", { "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" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
@@ -486,7 +487,7 @@
|
||||
|
||||
"glob-parent": ["glob-parent@6.0.2", "http://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
|
||||
"globals": ["globals@17.5.0", "", {}, "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g=="],
|
||||
"globals": ["globals@17.11.0", "", {}, "sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw=="],
|
||||
|
||||
"globalthis": ["globalthis@1.0.4", "http://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
|
||||
|
||||
@@ -504,9 +505,9 @@
|
||||
|
||||
"hasown": ["hasown@2.0.2", "http://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
|
||||
"hermes-estree": ["hermes-estree@0.25.1", "http://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
|
||||
|
||||
"hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
|
||||
"hermes-parser": ["hermes-parser@0.25.1", "http://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
|
||||
|
||||
"hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "http://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="],
|
||||
|
||||
@@ -584,7 +585,7 @@
|
||||
|
||||
"js-tokens": ["js-tokens@4.0.0", "http://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
|
||||
"js-yaml": ["js-yaml@4.1.1", "http://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
|
||||
"js-yaml": ["js-yaml@4.3.0", "http://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q=="],
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "http://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||
|
||||
@@ -596,7 +597,7 @@
|
||||
|
||||
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "http://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
|
||||
|
||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
"json5": ["json5@2.2.3", "http://registry.npmjs.org/json5/-/json5-2.2.3.tgz", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"jsx-ast-utils": ["jsx-ast-utils@3.3.5", "http://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="],
|
||||
|
||||
@@ -604,29 +605,29 @@
|
||||
|
||||
"levn": ["levn@0.4.1", "http://registry.npmjs.org/levn/-/levn-0.4.1.tgz", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.32.0", "http://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
|
||||
"lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="],
|
||||
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "http://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "http://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "http://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "http://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "http://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "http://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "http://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "http://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="],
|
||||
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "http://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "http://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "http://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="],
|
||||
|
||||
"lines-and-columns": ["lines-and-columns@1.2.4", "http://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||
|
||||
@@ -636,7 +637,7 @@
|
||||
|
||||
"loose-envify": ["loose-envify@1.4.0", "http://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": "cli.js" }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
||||
|
||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
"lru-cache": ["lru-cache@5.1.1", "http://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "http://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
@@ -650,7 +651,7 @@
|
||||
|
||||
"ms": ["ms@2.1.3", "http://registry.npmjs.org/ms/-/ms-2.1.3.tgz", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "http://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
"nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="],
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "http://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||
|
||||
@@ -658,7 +659,7 @@
|
||||
|
||||
"node-exports-info": ["node-exports-info@1.6.0", "http://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", { "dependencies": { "array.prototype.flatmap": "^1.3.3", "es-errors": "^1.3.0", "object.entries": "^1.1.9", "semver": "^6.3.1" } }, "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="],
|
||||
"node-releases": ["node-releases@2.0.37", "http://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="],
|
||||
|
||||
"object-assign": ["object-assign@4.1.1", "http://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||
|
||||
@@ -700,7 +701,7 @@
|
||||
|
||||
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "http://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
||||
|
||||
"postcss": ["postcss@8.5.9", "http://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw=="],
|
||||
"postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="],
|
||||
|
||||
"prelude-ls": ["prelude-ls@1.2.1", "http://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||
|
||||
@@ -710,21 +711,21 @@
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "http://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
|
||||
"react": ["react@19.2.5", "http://registry.npmjs.org/react/-/react-19.2.5.tgz", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="],
|
||||
"react": ["react@19.2.8", "http://registry.npmjs.org/react/-/react-19.2.8.tgz", {}, "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw=="],
|
||||
|
||||
"react-dom": ["react-dom@19.2.5", "http://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.5" } }, "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag=="],
|
||||
"react-dom": ["react-dom@19.2.8", "http://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.8" } }, "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ=="],
|
||||
|
||||
"react-is": ["react-is@19.2.5", "http://registry.npmjs.org/react-is/-/react-is-19.2.5.tgz", {}, "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ=="],
|
||||
"react-is": ["react-is@19.2.8", "http://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", {}, "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ=="],
|
||||
|
||||
"react-transition-group": ["react-transition-group@4.4.5", "http://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="],
|
||||
|
||||
"readdirp": ["readdirp@4.1.2", "http://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||
"readdirp": ["readdirp@5.0.0", "http://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
|
||||
|
||||
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "http://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
|
||||
|
||||
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "http://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", { "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" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
|
||||
|
||||
"reselect": ["reselect@5.1.1", "http://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="],
|
||||
"reselect": ["reselect@5.2.0", "http://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz", {}, "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw=="],
|
||||
|
||||
"resolve": ["resolve@2.0.0-next.6", "http://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA=="],
|
||||
|
||||
@@ -732,7 +733,7 @@
|
||||
|
||||
"reusify": ["reusify@1.1.0", "http://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||
|
||||
"rolldown": ["rolldown@1.0.0-rc.15", "http://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", { "dependencies": { "@oxc-project/types": "=0.124.0", "@rolldown/pluginutils": "1.0.0-rc.15" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.15", "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", "@rolldown/binding-darwin-x64": "1.0.0-rc.15", "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g=="],
|
||||
"rolldown": ["rolldown@1.2.4", "", { "dependencies": { "@oxc-project/types": "=0.144.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.2.4", "@rolldown/binding-darwin-arm64": "1.2.4", "@rolldown/binding-darwin-x64": "1.2.4", "@rolldown/binding-freebsd-x64": "1.2.4", "@rolldown/binding-linux-arm-gnueabihf": "1.2.4", "@rolldown/binding-linux-arm64-gnu": "1.2.4", "@rolldown/binding-linux-arm64-musl": "1.2.4", "@rolldown/binding-linux-ppc64-gnu": "1.2.4", "@rolldown/binding-linux-s390x-gnu": "1.2.4", "@rolldown/binding-linux-x64-gnu": "1.2.4", "@rolldown/binding-linux-x64-musl": "1.2.4", "@rolldown/binding-openharmony-arm64": "1.2.4", "@rolldown/binding-win32-arm64-msvc": "1.2.4", "@rolldown/binding-win32-x64-msvc": "1.2.4" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w=="],
|
||||
|
||||
"run-parallel": ["run-parallel@1.2.0", "http://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||
|
||||
@@ -742,7 +743,7 @@
|
||||
|
||||
"safe-regex-test": ["safe-regex-test@1.1.0", "http://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
|
||||
|
||||
"sass": ["sass@1.99.0", "http://registry.npmjs.org/sass/-/sass-1.99.0.tgz", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.1.5", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": "sass.js" }, "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q=="],
|
||||
"sass": ["sass@1.102.0", "http://registry.npmjs.org/sass/-/sass-1.102.0.tgz", { "dependencies": { "chokidar": "^5.0.0", "immutable": "^5.1.5", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-NSOyTnaQF7rTAEOtI2fwb386vL+akyiQLBZu8Na7hXCb+umJy0GAqlcMIaqACZ6Z1VgTBS4K9PG6B3IdjHGJsw=="],
|
||||
|
||||
"scheduler": ["scheduler@0.27.0", "http://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||
|
||||
@@ -790,14 +791,12 @@
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "http://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.16", "http://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
|
||||
"tinyglobby": ["tinyglobby@0.2.17", "http://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "http://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"totalist": ["totalist@3.0.1", "http://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "http://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"type-check": ["type-check@0.4.0", "http://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
|
||||
"typed-array-buffer": ["typed-array-buffer@1.0.3", "http://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
|
||||
@@ -810,13 +809,13 @@
|
||||
|
||||
"unbox-primitive": ["unbox-primitive@1.1.0", "http://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||
"update-browserslist-db": ["update-browserslist-db@1.2.3", "http://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "http://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"use-sync-external-store": ["use-sync-external-store@1.6.0", "http://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
|
||||
|
||||
"vite": ["vite@8.0.8", "http://registry.npmjs.org/vite/-/vite-8.0.8.tgz", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.15", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw=="],
|
||||
"vite": ["vite@8.2.1", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.25", "rolldown": "~1.2.1", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw=="],
|
||||
|
||||
"vite-plugin-symfony": ["vite-plugin-symfony@8.2.4", "http://registry.npmjs.org/vite-plugin-symfony/-/vite-plugin-symfony-8.2.4.tgz", { "dependencies": { "debug": "^4.4.1", "fast-glob": "^3.3.3", "picocolors": "^1.1.1", "sirv": "^3.0.1" }, "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-ph98EMPx8FhA6QIp43ZiK0zjODV9jumB7EHXfZEhRme2lo9oBa9sAXCNCCIvdVk/m9EWkNZpcRBjequjXZiSuA=="],
|
||||
|
||||
@@ -832,15 +831,17 @@
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "http://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
"yallist": ["yallist@3.1.1", "http://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"yaml": ["yaml@1.10.3", "http://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", {}, "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "http://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||
"zod": ["zod@4.3.6", "http://registry.npmjs.org/zod/-/zod-4.3.6.tgz", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||
|
||||
"zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
|
||||
"zod-validation-error": ["zod-validation-error@4.0.2", "http://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
|
||||
|
||||
"@base-ui/utils/@babel/runtime": ["@babel/runtime@7.29.7", "http://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
|
||||
|
||||
"@emotion/babel-plugin/convert-source-map": ["convert-source-map@1.9.0", "http://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
|
||||
|
||||
@@ -850,11 +851,31 @@
|
||||
|
||||
"@eslint/eslintrc/globals": ["globals@14.0.0", "http://registry.npmjs.org/globals/-/globals-14.0.0.tgz", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
|
||||
|
||||
"@mui/material/@babel/runtime": ["@babel/runtime@7.29.7", "http://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
|
||||
|
||||
"@mui/private-theming/@babel/runtime": ["@babel/runtime@7.29.7", "http://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
|
||||
|
||||
"@mui/styled-engine/@babel/runtime": ["@babel/runtime@7.29.7", "http://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
|
||||
|
||||
"@mui/system/@babel/runtime": ["@babel/runtime@7.29.7", "http://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
|
||||
|
||||
"@mui/types/@babel/runtime": ["@babel/runtime@7.29.7", "http://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
|
||||
|
||||
"@mui/utils/@babel/runtime": ["@babel/runtime@7.29.7", "http://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
|
||||
|
||||
"@mui/x-charts/@babel/runtime": ["@babel/runtime@7.29.7", "http://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
|
||||
|
||||
"@mui/x-charts-vendor/@babel/runtime": ["@babel/runtime@7.29.7", "http://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
|
||||
|
||||
"@mui/x-internal-gestures/@babel/runtime": ["@babel/runtime@7.29.7", "http://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
|
||||
|
||||
"@mui/x-internals/@babel/runtime": ["@babel/runtime@7.29.7", "http://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
|
||||
|
||||
"babel-plugin-macros/resolve": ["resolve@1.22.12", "http://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="],
|
||||
|
||||
"eslint/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "http://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
|
||||
|
||||
"eslint/espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="],
|
||||
"eslint/espree": ["espree@11.2.0", "http://registry.npmjs.org/espree/-/espree-11.2.0.tgz", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="],
|
||||
|
||||
"eslint/minimatch": ["minimatch@10.2.5", "http://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
@@ -866,7 +887,7 @@
|
||||
|
||||
"prop-types/react-is": ["react-is@16.13.1", "http://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||
|
||||
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.15", "http://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", {}, "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g=="],
|
||||
"vite/picomatch": ["picomatch@4.0.5", "http://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="],
|
||||
|
||||
"@eslint/config-array/minimatch/brace-expansion": ["brace-expansion@5.0.5", "http://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="],
|
||||
|
||||
|
||||
+120
-19
@@ -6,10 +6,11 @@ services:
|
||||
restart: unless-stopped
|
||||
container_name: '${APP_NAME}-app'
|
||||
ports:
|
||||
- "10080:80"
|
||||
- "${APP_PUBLISHED_PORT:-10080}:80"
|
||||
environment:
|
||||
SERVER_NAME: ${SERVER_NAME:-:80}
|
||||
APP_ENV: prod
|
||||
APP_VERSION: ${APP_VERSION:-1.0.0}
|
||||
APP_SECRET: ${APP_SECRET}
|
||||
APP_PUBLIC_HOSTNAME: ${APP_PUBLIC_HOSTNAME:-localhost}
|
||||
APP_CONTACT_MAIL_ADDRESS: ${APP_CONTACT_MAIL_ADDRESS:-7system7@gmail.com}
|
||||
@@ -25,14 +26,14 @@ services:
|
||||
MERCURE_JWT_TOKEN: ${MERCURE_JWT_TOKEN}
|
||||
MERCURE_SUBSCRIBER_JWT: ${MERCURE_SUBSCRIBER_JWT}
|
||||
MAILER_DSN: smtp://mail:25?verify_peer=0
|
||||
RECAPTCHA_SITE_KEY: ${RECAPTCHA_SITE_KEY}
|
||||
RECAPTCHA_SECRET_KEY: ${RECAPTCHA_SECRET_KEY}
|
||||
CAP_API_ENDPOINT: ${CAP_API_ENDPOINT}
|
||||
CAP_SECRET_KEY: ${CAP_SECRET_KEY}
|
||||
WEBAUTHN_RP_ID: ${WEBAUTHN_RP_ID:-localhost}
|
||||
WEBAUTHN_ORIGIN: ${WEBAUTHN_ORIGIN:-https://localhost}
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||
MINIO_ENDPOINT: http://minio:9000
|
||||
MINIO_PUBLIC_URL: ${MINIO_PUBLIC_URL:-http://localhost:9000}
|
||||
MINIO_ENDPOINT: http://rustfs:9000
|
||||
MINIO_PUBLIC_URL: ${MINIO_PUBLIC_URL:-http://localhost:9010}
|
||||
# IMPORTANT: Set TRUSTED_PROXIES to your reverse proxy IP in production.
|
||||
# For Docker on same host, use: 172.17.0.1 (default bridge) or 172.16.0.0/12 (overlay network)
|
||||
# For Kubernetes or external proxy, use the proxy's IP address.
|
||||
@@ -42,19 +43,113 @@ services:
|
||||
- app_var:/app/var
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1/ -o /dev/null"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
start_period: 60s
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
mail:
|
||||
condition: service_started
|
||||
rustfs:
|
||||
condition: service_healthy
|
||||
storage_init:
|
||||
condition: service_completed_successfully
|
||||
scheduler:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
restart: unless-stopped
|
||||
container_name: '${APP_NAME}-scheduler'
|
||||
command: ['php', 'bin/console', 'messenger:consume', 'scheduler_default', '--env=prod', '--no-interaction', '--memory-limit=128M']
|
||||
environment:
|
||||
APP_ENV: prod
|
||||
APP_SECRET: ${APP_SECRET}
|
||||
APP_PUBLIC_HOSTNAME: ${APP_PUBLIC_HOSTNAME:-localhost}
|
||||
DATABASE_URL: >-
|
||||
postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?serverVersion=${POSTGRES_VERSION}&charset=utf8
|
||||
POSTGRES_URL: db
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
MERCURE_URL: http://localhost/.well-known/mercure
|
||||
MERCURE_PUBLIC_URL: https://${APP_PUBLIC_HOSTNAME:-localhost}/.well-known/mercure
|
||||
MERCURE_JWT_SECRET: ${MERCURE_JWT_SECRET}
|
||||
MERCURE_JWT_TOKEN: ${MERCURE_JWT_TOKEN}
|
||||
MERCURE_SUBSCRIBER_JWT: ${MERCURE_SUBSCRIBER_JWT}
|
||||
MAILER_DSN: smtp://mail:25?verify_peer=0
|
||||
CAP_API_ENDPOINT: ${CAP_API_ENDPOINT}
|
||||
CAP_SECRET_KEY: ${CAP_SECRET_KEY}
|
||||
WEBAUTHN_RP_ID: ${WEBAUTHN_RP_ID:-localhost}
|
||||
WEBAUTHN_ORIGIN: ${WEBAUTHN_ORIGIN:-https://localhost}
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||
MINIO_ENDPOINT: http://rustfs:9000
|
||||
MINIO_PUBLIC_URL: ${MINIO_PUBLIC_URL:-http://localhost:9010}
|
||||
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.1}
|
||||
volumes:
|
||||
- app_var:/app/var
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
mail:
|
||||
condition: service_started
|
||||
rustfs:
|
||||
condition: service_healthy
|
||||
storage_init:
|
||||
condition: service_completed_successfully
|
||||
healthcheck:
|
||||
disable: true
|
||||
|
||||
rustfs:
|
||||
image: rustfs/rustfs:1.0.3
|
||||
restart: unless-stopped
|
||||
container_name: '${APP_NAME}-rustfs'
|
||||
environment:
|
||||
RUSTFS_ACCESS_KEY: ${MINIO_ROOT_USER}
|
||||
RUSTFS_SECRET_KEY: ${MINIO_ROOT_PASSWORD}
|
||||
RUSTFS_ADDRESS: ':9000'
|
||||
RUSTFS_CONSOLE_ADDRESS: ':9001'
|
||||
RUSTFS_CONSOLE_ENABLE: 'true'
|
||||
volumes:
|
||||
- rustfs_data:/data
|
||||
ports:
|
||||
- "${RUSTFS_PUBLISHED_API_PORT:-9010}:9000"
|
||||
- "${RUSTFS_PUBLISHED_CONSOLE_PORT:-9011}:9001"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9000/health"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
start_period: 15s
|
||||
storage_init:
|
||||
build:
|
||||
context: ./docker/storage-migrate
|
||||
entrypoint: ["python"]
|
||||
command: ["/usr/local/bin/storage-init.py"]
|
||||
restart: "no"
|
||||
container_name: '${APP_NAME}-storage-init'
|
||||
depends_on:
|
||||
rustfs:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
TARGET_ENDPOINT: http://rustfs:9000
|
||||
STORAGE_ACCESS_KEY: ${MINIO_ROOT_USER}
|
||||
STORAGE_SECRET_KEY: ${MINIO_ROOT_PASSWORD}
|
||||
STORAGE_BUCKET: mineseeker
|
||||
minio:
|
||||
image: minio/minio:RELEASE.2025-09-07T16-13-09Z-cpuv1
|
||||
profiles: ["storage-migration"]
|
||||
image: ${MINIO_IMAGE:-minio/minio:RELEASE.2025-09-07T16-13-09Z-cpuv1}
|
||||
pull_policy: never
|
||||
restart: unless-stopped
|
||||
container_name: '${APP_NAME}-minio'
|
||||
command: server /data --console-address ":9001"
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
- "${MINIO_PUBLISHED_API_PORT:-9012}:9000"
|
||||
- "${MINIO_PUBLISHED_CONSOLE_PORT:-9013}:9001"
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||
@@ -66,19 +161,25 @@ services:
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 10s
|
||||
minio_init:
|
||||
image: minio/minio:RELEASE.2025-09-07T16-13-09Z-cpuv1
|
||||
storage_migrate:
|
||||
profiles: ["storage-migration"]
|
||||
build:
|
||||
context: ./docker/storage-migrate
|
||||
restart: "no"
|
||||
container_name: '${APP_NAME}-minio-init'
|
||||
container_name: '${APP_NAME}-storage-migrate'
|
||||
depends_on:
|
||||
minio:
|
||||
condition: service_healthy
|
||||
rustfs:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||
SOURCE_ENDPOINT: http://minio:9000
|
||||
TARGET_ENDPOINT: http://rustfs:9000
|
||||
STORAGE_ACCESS_KEY: ${MINIO_ROOT_USER}
|
||||
STORAGE_SECRET_KEY: ${MINIO_ROOT_PASSWORD}
|
||||
STORAGE_BUCKET: mineseeker
|
||||
volumes:
|
||||
- ./docker/minio-init.sh:/minio-init.sh:ro
|
||||
entrypoint: ["/bin/sh", "/minio-init.sh"]
|
||||
- storage_migration_state:/state
|
||||
mail:
|
||||
image: boky/postfix:latest
|
||||
restart: unless-stopped
|
||||
@@ -105,7 +206,7 @@ services:
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- postgres_data:/var/lib/postgresql
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}" ]
|
||||
interval: 5s
|
||||
@@ -113,7 +214,7 @@ services:
|
||||
retries: 10
|
||||
start_period: 10s
|
||||
ports:
|
||||
- "15432:5432"
|
||||
- "${POSTGRES_PUBLISHED_PORT:-5452}:5432"
|
||||
volumes:
|
||||
app_var:
|
||||
postgres_data:
|
||||
@@ -121,5 +222,5 @@ volumes:
|
||||
caddy_config:
|
||||
postfix_spool:
|
||||
minio_data:
|
||||
|
||||
|
||||
rustfs_data:
|
||||
storage_migration_state:
|
||||
|
||||
+31
-24
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "splendid-bear/mineseeker",
|
||||
"version": "2026.2.1",
|
||||
"version": "2026.3.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"author": "https://www.splendidbear.org",
|
||||
"bugs": "https://source.splendidbear.org",
|
||||
@@ -10,14 +10,14 @@
|
||||
"prefer-stable": true,
|
||||
"private": true,
|
||||
"require": {
|
||||
"php": ">=8.5",
|
||||
"php": ">=8.5.12",
|
||||
"ext-gd": "*",
|
||||
"ext-iconv": "*",
|
||||
"ext-json": "*",
|
||||
"ext-gd": "*",
|
||||
"doctrine/dbal": "^3.7",
|
||||
"doctrine/doctrine-bundle": ">=2.11 <2.14",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.0",
|
||||
"doctrine/orm": "^2.6",
|
||||
"doctrine/dbal": "^4.3",
|
||||
"doctrine/doctrine-bundle": "^3.2",
|
||||
"doctrine/doctrine-migrations-bundle": "^4.0",
|
||||
"doctrine/orm": "^3.5",
|
||||
"endroid/qr-code": "^6.1",
|
||||
"firebase/php-jwt": "^7.0",
|
||||
"league/flysystem-aws-s3-v3": "^3.0",
|
||||
@@ -27,28 +27,35 @@
|
||||
"scheb/2fa-backup-code": "^8.5",
|
||||
"scheb/2fa-bundle": "^8.5",
|
||||
"scheb/2fa-totp": "^8.5",
|
||||
"symfony/console": "7.4.*",
|
||||
"symfony/console": "8.1.*",
|
||||
"symfony/flex": "^2.10.0",
|
||||
"symfony/form": "7.4.*",
|
||||
"symfony/framework-bundle": "7.4.*",
|
||||
"symfony/http-client": "7.4.*",
|
||||
"symfony/mailer": "7.4.*",
|
||||
"symfony/mercure": "^0.6",
|
||||
"symfony/mercure-bundle": "*",
|
||||
"symfony/monolog-bundle": "^3.8",
|
||||
"symfony/security-bundle": "7.4.*",
|
||||
"symfony/translation": "7.4.*",
|
||||
"symfony/twig-bundle": "7.4.*",
|
||||
"symfony/validator": "7.4.*",
|
||||
"symfony/yaml": "7.4.*",
|
||||
"symfony/form": "8.1.*",
|
||||
"symfony/framework-bundle": "8.1.*",
|
||||
"symfony/http-client": "8.1.*",
|
||||
"symfony/mailer": "8.1.*",
|
||||
"symfony/mercure": "^0.9",
|
||||
"symfony/mercure-bundle": "^0.6",
|
||||
"symfony/messenger": "8.1.*",
|
||||
"symfony/monolog-bundle": "^4.0",
|
||||
"symfony/scheduler": "8.1.*",
|
||||
"symfony/security-bundle": "8.1.*",
|
||||
"symfony/translation": "8.1.*",
|
||||
"symfony/twig-bundle": "8.1.*",
|
||||
"symfony/validator": "8.1.*",
|
||||
"symfony/yaml": "8.1.*",
|
||||
"web-auth/webauthn-framework": "^5.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"dama/doctrine-test-bundle": "^8.6",
|
||||
"phpunit/phpunit": "^13.1",
|
||||
"roave/security-advisories": "dev-master",
|
||||
"symfony/dotenv": "7.4.*",
|
||||
"symfony/browser-kit": "8.1.*",
|
||||
"symfony/css-selector": "8.1.*",
|
||||
"symfony/dotenv": "8.1.*",
|
||||
"symfony/maker-bundle": "^1.5",
|
||||
"symfony/stopwatch": "7.4.*",
|
||||
"symfony/web-profiler-bundle": "7.4.*"
|
||||
"symfony/stopwatch": "8.1.*",
|
||||
"symfony/web-profiler-bundle": "8.1.*",
|
||||
"zenstruck/foundry": "^2.9"
|
||||
},
|
||||
"config": {
|
||||
"preferred-install": {
|
||||
@@ -97,7 +104,7 @@
|
||||
"extra": {
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"require": "7.4.*"
|
||||
"require": "8.1.*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+4540
-1962
File diff suppressed because it is too large
Load Diff
@@ -16,4 +16,6 @@ return [
|
||||
Scheb\TwoFactorBundle\SchebTwoFactorBundle::class => ['all' => true],
|
||||
League\FlysystemBundle\FlysystemBundle::class => ['all' => true],
|
||||
Liip\ImagineBundle\LiipImagineBundle::class => ['all' => true],
|
||||
Zenstruck\Foundry\ZenstruckFoundryBundle::class => ['dev' => true, 'test' => true],
|
||||
DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true],
|
||||
];
|
||||
|
||||
@@ -11,9 +11,12 @@ doctrine:
|
||||
url: '%env(resolve:DATABASE_URL)%'
|
||||
|
||||
orm:
|
||||
auto_generate_proxy_classes: '%kernel.debug%'
|
||||
enable_native_lazy_objects: true
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore
|
||||
auto_mapping: true
|
||||
schema_ignore_classes:
|
||||
- App\Entity\UserStats
|
||||
- App\Entity\RecentBattle
|
||||
mappings:
|
||||
App:
|
||||
is_bundle: false
|
||||
|
||||
@@ -3,6 +3,7 @@ mercure:
|
||||
default:
|
||||
url: '%env(MERCURE_URL)%'
|
||||
public_url: '%env(MERCURE_PUBLIC_URL)%'
|
||||
protocol_version: '0.x'
|
||||
jwt:
|
||||
secret: '%env(MERCURE_JWT_SECRET)%'
|
||||
publish: '*'
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
framework:
|
||||
messenger:
|
||||
# Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
|
||||
# failure_transport: failed
|
||||
|
||||
transports:
|
||||
# https://symfony.com/doc/current/messenger.html#transport-configuration
|
||||
# async: '%env(MESSENGER_TRANSPORT_DSN)%'
|
||||
# failed: 'doctrine://default?queue_name=failed'
|
||||
sync: 'sync://'
|
||||
|
||||
routing:
|
||||
# Route your messages to the transports
|
||||
# 'App\Message\YourMessage': async
|
||||
|
||||
# when@test:
|
||||
# framework:
|
||||
# messenger:
|
||||
# transports:
|
||||
# # replace with your transport name here (e.g., my_transport: 'in-memory://')
|
||||
# # For more Messenger testing tools, see https://github.com/zenstruck/messenger-test
|
||||
# async: 'in-memory://'
|
||||
@@ -1,4 +1,4 @@
|
||||
framework:
|
||||
test: true
|
||||
session:
|
||||
storage_id: session.storage.mock_file
|
||||
storage_factory_id: session.storage.factory.mock_file
|
||||
|
||||
@@ -4,4 +4,4 @@ twig:
|
||||
strict_variables: '%kernel.debug%'
|
||||
globals:
|
||||
version: "%jotunheimr.version%"
|
||||
recaptcha_site_key: "%env(RECAPTCHA_SITE_KEY)%"
|
||||
cap_api_endpoint: "%env(CAP_API_ENDPOINT)%"
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
when@dev: &dev
|
||||
# See full configuration: https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#full-default-bundle-configuration
|
||||
zenstruck_foundry:
|
||||
enable_auto_refresh_with_lazy_objects: true
|
||||
persistence:
|
||||
# Flush only once per call of `PersistentObjectFactory::create()`
|
||||
flush_once: true
|
||||
|
||||
# If you use the `make:factory --test` command, you may need to uncomment the following.
|
||||
# See https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#generate
|
||||
#services:
|
||||
# App\Tests\Factory\:
|
||||
# resource: '%kernel.project_dir%/tests/Factory/'
|
||||
# autowire: true
|
||||
# autoconfigure: true
|
||||
|
||||
when@test: *dev
|
||||
@@ -1,3 +1,3 @@
|
||||
_errors:
|
||||
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
|
||||
resource: '@FrameworkBundle/Resources/config/routing/errors.php'
|
||||
prefix: /_error
|
||||
@@ -1,7 +1,7 @@
|
||||
web_profiler_wdt:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.php'
|
||||
prefix: /_wdt
|
||||
|
||||
web_profiler_profiler:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.php'
|
||||
prefix: /_profiler
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
|
||||
parameters:
|
||||
locale: 'en'
|
||||
jotunheimr.version: 1.1.0-20191026
|
||||
jotunheimr.version: '%env(APP_VERSION)%'
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
@@ -28,6 +28,7 @@ services:
|
||||
App\Service\BattleCardGenerator:
|
||||
arguments:
|
||||
$cacheDir: '%kernel.project_dir%/var/og-cache'
|
||||
$fontPath: '%kernel.project_dir%/assets/fonts/Carlito-Bold.ttf'
|
||||
$minioMediaStorage: '@mineseeker.media.storage'
|
||||
|
||||
Aws\S3\S3Client:
|
||||
|
||||
@@ -8,6 +8,11 @@ until php -r "new PDO('pgsql:host=db;port=5432;dbname=${POSTGRES_DB}', '${POSTGR
|
||||
done
|
||||
echo "[entrypoint] Database is ready."
|
||||
|
||||
if [ "$#" -gt 0 ]; then
|
||||
echo "[entrypoint] Starting worker command: $*"
|
||||
exec "$@"
|
||||
fi
|
||||
|
||||
echo "[entrypoint] Clearing and warming Symfony cache..."
|
||||
php bin/console cache:clear
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
echo "[minio-init] Configuring alias..."
|
||||
mc alias set local http://minio:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"
|
||||
echo "[minio-init] Waiting for MinIO..."
|
||||
until mc alias set local http://minio:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" > /dev/null 2>&1; do
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "[minio-init] Configuring bucket..."
|
||||
|
||||
if mc ls local/mineseeker > /dev/null 2>&1; then
|
||||
echo "[minio-init] Already initialized, skipping."
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user