Private
Public Access
Compare commits
104
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 | ||
|
|
f72cd45afd | ||
|
|
51bd909879 | ||
|
|
db37ab45b2 | ||
|
|
9256db7f8c | ||
|
|
d9059acb78 | ||
|
|
5da8a04c18 | ||
|
|
ba8a0befb0 | ||
|
|
5ac291de81 | ||
|
|
991b114a3c | ||
|
|
c79584c7d2 | ||
|
|
e77c8a8f7c | ||
|
|
c2308ba408 | ||
|
|
e5a22cdfe3 | ||
|
|
09b0d21621 | ||
|
|
9aef27a0eb | ||
|
|
c00ed57240 | ||
|
|
ef4cf6ef69 | ||
|
|
dc9c5f6545 | ||
|
|
25f2aaab8c | ||
|
|
0cc9cdaf07 | ||
|
|
247f437445 | ||
|
|
0e94367223 | ||
|
|
a9ee28b395 | ||
|
|
bd074c5c9d | ||
|
|
42c552c528 | ||
|
|
3b376e5386 | ||
|
|
45a8e6b4a1 | ||
|
|
1f8e9c3c56 | ||
|
|
a511b86db8 | ||
|
|
1c0ad054bb | ||
|
|
5a8799bb7f | ||
|
|
6c443d8e86 | ||
|
|
8795fedda9 | ||
|
|
588fb57299 | ||
|
|
eb345e17ca | ||
|
|
c2693c4648 | ||
|
|
43efc16562 | ||
|
|
80d6440ece | ||
|
|
5ee972f003 | ||
|
|
6f3edb41ea | ||
|
|
c52939a7a3 | ||
|
|
573d409606 | ||
|
|
9a58bc9a5e | ||
|
|
8780800dff | ||
|
|
f442942faf | ||
|
|
a61d881a4e |
@@ -6,25 +6,36 @@
|
|||||||
APP_ENV=dev
|
APP_ENV=dev
|
||||||
APP_SECRET=changethis
|
APP_SECRET=changethis
|
||||||
APP_NAME=mineseeker
|
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
|
||||||
|
# TRUSTED_PROXIES: Only needed for bare-metal dev behind a reverse proxy
|
||||||
|
# For Docker development, this is set in compose.override.yaml
|
||||||
|
# For production, set in PROD_ENV_FILE Gitea secret (use 172.18.0.0/16 initially)
|
||||||
#TRUSTED_PROXIES=127.0.0.1,127.0.0.2
|
#TRUSTED_PROXIES=127.0.0.1,127.0.0.2
|
||||||
#TRUSTED_HOSTS=localhost,example.com
|
#TRUSTED_HOSTS=localhost,example.com
|
||||||
###< symfony/framework-bundle ###
|
###< symfony/framework-bundle ###
|
||||||
|
|
||||||
###> doctrine/doctrine-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.
|
# 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 ###
|
###< doctrine/doctrine-bundle ###
|
||||||
|
|
||||||
###> google/recaptcha ###
|
###> trycap.dev/cap ###
|
||||||
RECAPTCHA_SITE_KEY=changethis
|
# CAP_API_ENDPOINT: Full URL including site-key path, e.g. https://cap.example.com/my-site-key/
|
||||||
RECAPTCHA_SECRET_KEY=changethis
|
# After deploying the Cap service, create a site in the dashboard and paste the endpoint here.
|
||||||
###< google/recaptcha ###
|
CAP_API_ENDPOINT=http://localhost:3000/changethis/
|
||||||
|
CAP_SECRET_KEY=changethis
|
||||||
|
###< trycap.dev/cap ###
|
||||||
|
|
||||||
###> minio/minio ###
|
###> minio/minio ###
|
||||||
MINIO_ROOT_USER=changethis
|
MINIO_ROOT_USER=changethis
|
||||||
MINIO_ROOT_PASSWORD=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_PUBLIC_URL=https://your-minio-subdomain.example.com
|
||||||
###< minio/minio ###
|
###< minio/minio ###
|
||||||
|
|
||||||
@@ -36,9 +47,9 @@ MAIL_DOMAIN=localhost
|
|||||||
###> symfony/mercure-bundle ###
|
###> symfony/mercure-bundle ###
|
||||||
# See https://symfony.com/doc/current/mercure.html#configuration
|
# 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)
|
# 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
|
# 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
|
# The secret used to sign the JWTs
|
||||||
MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"
|
MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"
|
||||||
# Publisher JWT (must match publisher_jwt in your Caddyfile)
|
# Publisher JWT (must match publisher_jwt in your Caddyfile)
|
||||||
@@ -48,6 +59,13 @@ MERCURE_SUBSCRIBER_JWT=changethis
|
|||||||
###< symfony/mercure-bundle ###
|
###< symfony/mercure-bundle ###
|
||||||
|
|
||||||
###> web-auth/webauthn-framework ###
|
###> web-auth/webauthn-framework ###
|
||||||
WEBAUTHN_RP_ID=mine.local
|
WEBAUTHN_RP_ID=mine.home.arpa
|
||||||
WEBAUTHN_ORIGIN=https://mine.local
|
WEBAUTHN_ORIGIN=https://mine.home.arpa
|
||||||
###< web-auth/webauthn-framework ###
|
###< 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,32 @@
|
|||||||
|
ignore_regexps = [
|
||||||
|
r'@minor', r'!minor',
|
||||||
|
r'@cosmetic', r'!cosmetic',
|
||||||
|
r'@refactor', r'!refactor',
|
||||||
|
r'@wip', r'!wip',
|
||||||
|
r'^(.{3,3}\s*:)?\s*[fF]irst commit.?\s*$',
|
||||||
|
r'^$', ## ignore commits with empty messages
|
||||||
|
r'@skipChangelog', r'!skipChangelog', r'skipChangeLog', r'!skipChangeLog',
|
||||||
|
r'Merge branch', r'Merge remote-tracking branch', r'!deploy',
|
||||||
|
]
|
||||||
|
section_regexps = [
|
||||||
|
('New', [
|
||||||
|
r'^[nN]ew\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
|
||||||
|
]),
|
||||||
|
('Changes', [
|
||||||
|
r'^[cC]hg\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
|
||||||
|
]),
|
||||||
|
('Fix', [
|
||||||
|
r'^[fF]ix\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
|
||||||
|
]),
|
||||||
|
('Other', None ## Match all lines
|
||||||
|
),
|
||||||
|
]
|
||||||
|
body_process = ReSub(r'((^|\n)[A-Z]\w+(-\w+)*: .*(\n\s+.*)*)+$', r'') | strip
|
||||||
|
subject_process = (strip |
|
||||||
|
ReSub(r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n@]*)(@[a-z]+\s+)*$', r'\4') |
|
||||||
|
SetIfEmpty("No commit message.") | ucfirst | final_dot)
|
||||||
|
tag_filter_regexp = r'^(v)?[0-9]+\.[0-9]+(\.[0-9]+)?(\-[0-9]+)?$'
|
||||||
|
unreleased_version_label = "(unreleased)"
|
||||||
|
output_engine = mustache("markdown")
|
||||||
|
include_merge = True
|
||||||
|
revs = []
|
||||||
@@ -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 }}"
|
cd "${{ vars.PROD_APP_DIR }}"
|
||||||
docker compose build
|
docker compose build
|
||||||
|
|
||||||
- name: Start services
|
- name: Migrate legacy MinIO and deploy
|
||||||
|
env:
|
||||||
|
APP_VERSION: ${{ vars.APP_VERSION }}
|
||||||
run: |
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
cd "${{ vars.PROD_APP_DIR }}"
|
cd "${{ vars.PROD_APP_DIR }}"
|
||||||
|
|
||||||
|
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
|
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 ###
|
###> system7 - jotunheimr ###
|
||||||
compose.override.yaml
|
compose.override.yaml
|
||||||
+bak/
|
+bak/
|
||||||
|
/backups/
|
||||||
.idea/
|
.idea/
|
||||||
node_modules/
|
node_modules/
|
||||||
nohup.out
|
nohup.out
|
||||||
@@ -14,3 +15,8 @@ nohup.out
|
|||||||
/var/
|
/var/
|
||||||
/vendor/
|
/vendor/
|
||||||
###< symfony/framework-bundle ###
|
###< 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! 🚀**
|
||||||
+600
-178
@@ -1,189 +1,611 @@
|
|||||||
Changelog
|
# Changelog
|
||||||
=========
|
|
||||||
|
|
||||||
|
|
||||||
(unreleased)
|
## v2026.3.1-2 (2026-09-21)
|
||||||
------------
|
|
||||||
|
|
||||||
New
|
### Changes
|
||||||
~~~
|
|
||||||
- Add timer for the acceptance of the challenge #4. [Lang]
|
|
||||||
- Registered users have avatars next to the timer #4. [Lang]
|
|
||||||
- Add opportunity to use profile picture. #4. [Lang]
|
|
||||||
- Add more stats and a dialog for the recent battle that can be
|
|
||||||
shareable #4. [Lang]
|
|
||||||
- Implement the 2FA authentication (TOTP and backup codes) #4. [Lang]
|
|
||||||
- Add beta logo to the corner #3. [Lang]
|
|
||||||
- Add mineseeker game to the symfony 4 project #3. [Lang]
|
|
||||||
- Upgrade to the latest symfony v4 #3. [Lang]
|
|
||||||
|
|
||||||
Changes
|
* Completely disable the contact form #15. [Lang]
|
||||||
~~~~~~~
|
|
||||||
- Protect the gameplay with recaptcha #4. [Lang]
|
|
||||||
- The waiting dialog is uncloseable until the time is up #4. [Lang]
|
|
||||||
- Add share button to the overlay when the game ends #4. [Lang]
|
|
||||||
- Make fancy og tags - and create a special one for battle sharing #4.
|
|
||||||
[Lang]
|
|
||||||
- The user's avatar will be saved as a uuid.extension #4. [Lang]
|
|
||||||
- Fix missing favicon #4. [Lang]
|
|
||||||
- Add modern Webauthn authentication #4. [Lang]
|
|
||||||
- Refactor all forms to have Symfony Form Types & Validation
|
|
||||||
Constrainsts - & implement Google ReCapthca v3 #4. [Lang]
|
|
||||||
- Add forgot password functionality #4. [Lang]
|
|
||||||
- Increase the minimum PHP version to the latest major - and massive
|
|
||||||
refactor on back-end, like Controllers and Repositories #4. [Lang]
|
|
||||||
- Redesign the resign dialog #4. [Lang]
|
|
||||||
- Re-implement the waiting for opponent dialog - refactor its gfx - &
|
|
||||||
add online user selection dialog #4. [Lang]
|
|
||||||
- Improve the gfx on homepage - implement login/register and activation
|
|
||||||
for authentication - and add the first version of profile page #4.
|
|
||||||
[Lang]
|
|
||||||
- Refactor and redesign the gfx on front-end #4. [Lang]
|
|
||||||
- Add timers to each player - renew the whole migration #4. [Lang]
|
|
||||||
- Use namespaces for front-end #4. [Lang]
|
|
||||||
- Replace webpack w/ vite & remove old, legacy jQuery from the code #4.
|
|
||||||
[Lang]
|
|
||||||
- More, massive refactor for front-end #4. [Lang]
|
|
||||||
- Massive refactor on front-end - and remove unnecessary deps #4. [Lang]
|
|
||||||
- Change the code style to fit the current standard #4. [Lang]
|
|
||||||
- Refactor to use Attributes instead of yaml markdown #4. [Lang]
|
|
||||||
- Outsource the Grid generation and interactions to the backend #4.
|
|
||||||
[Lang]
|
|
||||||
- Remove unnecessary variables and prune the Facebook registration
|
|
||||||
method #4. [Lang]
|
|
||||||
- Replace the legacy gos/web-socket-bundle & replace it with Mercure
|
|
||||||
protocol #4. [Lang]
|
|
||||||
- Created the first working solution since 7 yrs #4. [Lang]
|
|
||||||
- Make the first working version - the stepping is broken due to the
|
|
||||||
algorythm structure #4. [Lang]
|
|
||||||
- Change the composer default php minimum environment #3. [Lang]
|
|
||||||
- Change the default url to wss on frontend #3. [Lang]
|
|
||||||
- Refactor Rpc and Topic classes #3. [Lang]
|
|
||||||
- Refactor classes and reformat some layout #3. [Lang]
|
|
||||||
- Remove deprecated files #3. [Lang]
|
|
||||||
- Doc in README.md #3. [Lang]
|
|
||||||
- Gitignore a js.map file #2. [Lang]
|
|
||||||
|
|
||||||
Fix
|
|
||||||
~~~
|
|
||||||
- Missing font-awesome icons on bare-metal environment #4. [Lang]
|
|
||||||
- Quickfix for email sending #4. [Lang]
|
|
||||||
|
|
||||||
Other
|
|
||||||
~~~~~
|
|
||||||
- Hg: pkg: new version release !skipChangelog. [Lang]
|
|
||||||
- Pkg: usr: solve the not-working mailing on dev env under docker #4.
|
|
||||||
[Lang]
|
|
||||||
- Deploy version 1.1.0 !deploy #11. [Lang]
|
|
||||||
|
|
||||||
|
|
||||||
1.1.0 (2019-10-26)
|
## v2026.3.1-0 (2026-08-18)
|
||||||
------------------
|
|
||||||
|
|
||||||
Changes
|
### Changes
|
||||||
~~~~~~~
|
|
||||||
- Reinit project - disable redis module and make the project compatible
|
* Add backup/restore scripts #15. [Lang]
|
||||||
w/ PHP7.3 #2. [Lang]
|
|
||||||
|
* Fix the changing local url names from legacy .local - & change the minio default port #15. [Lang]
|
||||||
|
|
||||||
|
|
||||||
0.4.0 (2019-10-26)
|
## v2026.3.0-0 (2026-07-27)
|
||||||
------------------
|
|
||||||
- Change session driver to REDIS. [Lang]
|
### New
|
||||||
- Add created, updated field to db && improve graph design. [Lang]
|
|
||||||
- Cache setup && optimalize for google pagespeed && optimalize all
|
* Hardening the registration process with removing the not activated registrations #14. [Lang]
|
||||||
images. [Lang]
|
|
||||||
- Improve graph design on homepage && add footer and techs && add
|
### Changes
|
||||||
official pages. [Lang]
|
|
||||||
- Bugfix mine websocket periodic mysql calling. [Lang]
|
* Add current version of the app in the footer #14. [Lang]
|
||||||
- Bugfix hwioauth remember me && centralize hwioauth and facebook
|
|
||||||
settings. [Lang]
|
* Upgrade FE to the latest possible version #14. [Lang]
|
||||||
- Centralize jquery && bugfix mysql auto-termination problem w/ user
|
|
||||||
auth. [Lang]
|
* All tests passed and migrated to Symfony 8 #14. [Lang]
|
||||||
- Release beta4. [Lang]
|
|
||||||
- Gitignore npm debug log. [Lang]
|
* Upgrade the BE deps to the latest possible version #14. [Lang]
|
||||||
- Add english lang everywhere && add snowfall && add centralized version
|
|
||||||
nbr && improve stylesheet && slack integration. [Lang]
|
* Remove registration notification #14. [Lang]
|
||||||
- Bugfix #30 && random bg in game. [Lang]
|
|
||||||
- Add google analytics and facebook scripts && improve url share method
|
* Add a process that removes the unnecessary minio-init when it does not needed anymore #14. [Lang]
|
||||||
w/ fb && enforce https in prod. [Lang]
|
|
||||||
- Reg and login buttons on index && remove list method && facebook
|
* Removed the obsolete contact form entry - legacy code from Google ReCapthca #14. [Lang]
|
||||||
centralize. [Lang]
|
|
||||||
- Redesign user frontend. [Lang]
|
|
||||||
- Mods for performance; one js.min file on prod. [Lang]
|
## v2026.2.10-0 (2026-06-02)
|
||||||
- Improve webpack config for prod compile #23. [Lang]
|
|
||||||
- Ssl handling #22 && reconnection issues #20, #21. [Lang]
|
### Changes
|
||||||
- Facebook prod settings w/ app; hwi/HWIOAuthBundle. [Lang]
|
|
||||||
- Refact && game reconnection and restore w/o refresh #3 && bugfix bomb
|
* Add clickable badges for Cap app-wide #13. [Lang]
|
||||||
explosion on opponent mines #19. [Lang]
|
|
||||||
- Typo in rpc. [Lang]
|
* Upgrade both backend and frontend to the latest available version #13. [Lang]
|
||||||
- Handle prod mysql timeout && graphics improve. [Lang]
|
|
||||||
- Gitignore webpacked index.js. [Lang]
|
* Replace Google ReCaptcha with Cap instance #13. [Lang]
|
||||||
- Add production mods. [Lang]
|
|
||||||
- Bugfix points saving and exploded bombs to db && you can resign #6.
|
* Fix all eslint issues - & add example of the testing env #10. [Lang]
|
||||||
[Lang]
|
|
||||||
- Bugfix resign button existence #11. [Lang]
|
|
||||||
- Bugfix opponent bomb btn buzz on hover #10. [Lang]
|
## v2026.2.9-0 (2026-04-23)
|
||||||
- Bugfix points problem in the end #16. [Lang]
|
|
||||||
- Add desc to every user #9. [Lang]
|
### New
|
||||||
- Clipboard - not working #8. [Lang]
|
|
||||||
- Random player on start #5. [Lang]
|
* Add tracking code for the app #10. [Lang]
|
||||||
- Show left mines after end #2 && reduce network traffic && better
|
|
||||||
active field checking method. [Lang]
|
|
||||||
- Some refactor #13. [Lang]
|
## v2026.2.8-3 (2026-04-22)
|
||||||
- Bugfix grid field render #12. [Lang]
|
|
||||||
- Game ends after x mines. [Lang]
|
### Fix
|
||||||
- Add new sounds && refactor && new bg images && form redesigns. [Lang]
|
|
||||||
- Bugfix entities gridrow, grid && improve graph design on homepage.
|
* The error message cannot be seen during avatar changing #10. [Lang]
|
||||||
[Lang]
|
|
||||||
- Some refactor && prod settings. [Lang]
|
|
||||||
- Improve graphics design in game. [Lang]
|
## v2026.2.8-2 (2026-04-21)
|
||||||
- Bugfix grid row in entity. [Lang]
|
|
||||||
- Bugfix changePlayer after bomb explosion. [Lang]
|
### Changes
|
||||||
- Improve game graph design. [Lang]
|
|
||||||
- Login and register form more design. [Lang]
|
* Increase the 2 MB avatar maximum file size to 10 MB #10. [Lang]
|
||||||
- Add basic design to userbundle && refactor. [Lang]
|
|
||||||
- Add font-awesome. [Lang]
|
|
||||||
- Working user authentication w/ fb and plain login. [Lang]
|
## v2026.2.8-1 (2026-04-21)
|
||||||
- Add facebook login module, hwi/HWIOAuthBundle. [Lang]
|
|
||||||
- Login && register form overrided. [Lang]
|
### Changes
|
||||||
- Js and config refactor. [Lang]
|
|
||||||
- Replace gridcol object to json array in db. [Lang]
|
* Upgrade front-end & back-end deps to the latest available version #10. [Lang]
|
||||||
- Refactor. [Lang]
|
|
||||||
- Save steps and point info to db. [Lang]
|
|
||||||
- Save the step data to db. [Lang]
|
## v2026.2.8-0 (2026-04-21)
|
||||||
- Renamed the acme to mineseeker && handle when the user connection has
|
|
||||||
been lost. [Lang]
|
### New
|
||||||
- Add player names to UI. [Lang]
|
|
||||||
- Add overlay && game do not start until the opponent came. [Lang]
|
* Add CI/CD improvements - add new CI workflow - & improve the deployment w/ tests #10. [Lang]
|
||||||
- Add base64 encryption to grid when it has been sended to server.
|
|
||||||
[Lang]
|
* Add test cases to back-end w/ real database connection in it #10. [Lang]
|
||||||
- On click opponents bomb, you cannot target && refactor. [Lang]
|
|
||||||
- Warning when player has been found more than 20 mines. [Lang]
|
### Changes
|
||||||
- Bugfix center mine counter animation. [Lang]
|
|
||||||
- The opponent is the next when bomb is exploded. [Lang]
|
* The original CI/CD workflow is restored - the work with tests is postponed #10. [Lang]
|
||||||
- Current username checked && refactor && remove players in channel when
|
|
||||||
they are more than 2. [Lang]
|
* Add sound when the game start #10. [Lang]
|
||||||
- Send bomb info and use it on opponent. [Lang]
|
|
||||||
- Add sounds w/ howler. [Lang]
|
* Create AGENTS.md file for future maintenance #9. [Lang]
|
||||||
- Bugfix multiple empty fields w/ one click on opponent view. [Lang]
|
|
||||||
- Refact && remove sound and logging && bugfix BIGBUG - handleGridField
|
* 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]
|
||||||
and showAppropriateFields sort order... [Lang]
|
|
||||||
- Create first working communication. [Lang]
|
|
||||||
- Create entities and repositories. [Lang]
|
## v2026.2.7-1 (2026-04-21)
|
||||||
- Changed websocket default port && debug RPC. [Lang]
|
|
||||||
- Created working session and client handler w/ websocket. [Lang]
|
### Changes
|
||||||
- Working websocket client and server w/o session handling and storage.
|
|
||||||
[Lang]
|
* Fine-tune the recent battle list #8. [Lang]
|
||||||
- Composer update. [Lang]
|
|
||||||
- Improve game && start sound creating. [Lang]
|
|
||||||
- Refactor grid control and grid field. [Lang]
|
## v2026.2.7-0 (2026-04-21)
|
||||||
- Created basic game w/ table and animations. [Lang]
|
|
||||||
- Websocket basic setup FE & BE && working basic game w/ react &&
|
### Changes
|
||||||
webpack & babel config. [Lang]
|
|
||||||
- Gitignore node_modules && add symlink to node_modules (just for
|
* Small changes on docs - and improve text on homepage #8. [Lang]
|
||||||
install) && basic react. [Lang]
|
|
||||||
- Add react hello world. [Lang]
|
* Massive refactor on front-end for unification and readiness #8. [Lang]
|
||||||
- Rename project in config. [Lang]
|
|
||||||
- Initial commit && create project in symfony3. [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
|
||||||
|
|
||||||
|
* Add ReCaptcha overlay again to protect the game #7. [Lang]
|
||||||
|
|
||||||
|
* Upgrade all front-end packages to the latest available version - and fix all eslint warnings & errors #7. [Lang]
|
||||||
|
|
||||||
|
* Upgrade fe packages #7. [Lang]
|
||||||
|
|
||||||
|
* Massive refactor on fetches - create centralized dataProvider #7. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.5-0 (2026-04-19)
|
||||||
|
|
||||||
|
### New
|
||||||
|
|
||||||
|
* Add Firebase deps to back-end #7. [Lang]
|
||||||
|
|
||||||
|
* Add missing buttons for overlays #7. [Lang]
|
||||||
|
|
||||||
|
* A new feature came up - the abandoned plays can be restored, if both users are registered users #7. [Lang]
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
* Fix the '0' in Battle reports #6. [Lang]
|
||||||
|
|
||||||
|
* Fix missing icons on "Battle report" #6. [Lang]
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
* The bomb using was not recorded correctly - the old data will be corrupted #6. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.4-0 (2026-04-18)
|
||||||
|
|
||||||
|
### New
|
||||||
|
|
||||||
|
* Add new profile charts and stats - & add new logo to the tech stack #5. [Lang]
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
* Upgrade fe deps #5. [Lang]
|
||||||
|
|
||||||
|
* Improve the Battle reports to change unnecessary data with interesting data #5. [Lang]
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
* The react is crashing on some cases #5. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.3-0 (2026-04-18)
|
||||||
|
|
||||||
|
### New
|
||||||
|
|
||||||
|
* Add initialization bonus points' system to the gameplay #5. [Lang]
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
* Add extended data to battle reports and sharing image to make viewable bonus points #5. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.2-9 (2026-04-18)
|
||||||
|
|
||||||
|
### New
|
||||||
|
|
||||||
|
* Add rules page #4. [Lang]
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
* The font-awesome simplifying to work on bare-metal - & fix all warnings at build time #4. [Lang]
|
||||||
|
|
||||||
|
* The css problem had been solved on reponsive gfx on homepage #4. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.1-8 (2026-04-18)
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
* Quickfix for https-only login - & add user data when the user is not logged in #4. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.1-7 (2026-04-16)
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
* Add consent checkbox to user's registration - and fix the sharing pics #4. [Lang]
|
||||||
|
|
||||||
|
* Add correct version numbering and CHANGELOG - and add the LICENSE #4. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.1-6 (2026-04-16)
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
* Update all texts on all pages - extend them with the game specific things #4. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.1-5 (2026-04-16)
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
* The meta tags does not have https scheme - nothing worked in configuration #4. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.1-4 (2026-04-15)
|
||||||
|
|
||||||
|
### New
|
||||||
|
|
||||||
|
* Add notification email when a user is registered #4. [Lang]
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
* Add notification on activation too #4. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.1-2 (2026-04-15)
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
* Another attempt to fix the email assets #4. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.1-1 (2026-04-15)
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
* The images does not shows in emails #4. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.1-0 (2026-04-15)
|
||||||
|
|
||||||
|
### New
|
||||||
|
|
||||||
|
* Add Contact page with email sending behaviour #4. [Lang]
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
* Add missing .env variable and increase the version number and add missing data from front-end and back-end deps descriptor #4. [Lang]
|
||||||
|
|
||||||
|
* Change the shareable battle - add avatars to it - even on the og tags #4. [Lang]
|
||||||
|
|
||||||
|
* Change text #4. [Lang]
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
* The mailhog is crashed on development env #4. [Lang]
|
||||||
|
|
||||||
|
* The og tags did not have proper http schema - they should have https #4. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.0-5 (2026-04-14)
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
* Add donation button #4. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.0-4 (2026-04-14)
|
||||||
|
|
||||||
|
### New
|
||||||
|
|
||||||
|
* Add timer for the acceptance of the challenge #4. [Lang]
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
* Protect the gameplay with recaptcha #4. [Lang]
|
||||||
|
|
||||||
|
* The waiting dialog is uncloseable until the time is up #4. [Lang]
|
||||||
|
|
||||||
|
* Add share button to the overlay when the game ends #4. [Lang]
|
||||||
|
|
||||||
|
* Make fancy og tags - and create a special one for battle sharing #4. [Lang]
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
* Missing font-awesome icons on bare-metal environment #4. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.0-3 (2026-04-14)
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
* The user's avatar will be saved as a uuid.extension #4. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.0-1 (2026-04-14)
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
* Quickfix for email sending #4. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.0-0 (2026-04-14)
|
||||||
|
|
||||||
|
### New
|
||||||
|
|
||||||
|
* Registered users have avatars next to the timer #4. [Lang]
|
||||||
|
|
||||||
|
* Add opportunity to use profile picture. #4. [Lang]
|
||||||
|
|
||||||
|
* Add more stats and a dialog for the recent battle that can be shareable #4. [Lang]
|
||||||
|
|
||||||
|
* Implement the 2FA authentication (TOTP and backup codes) #4. [Lang]
|
||||||
|
|
||||||
|
* Add beta logo to the corner #3. [Lang]
|
||||||
|
|
||||||
|
* Add mineseeker game to the symfony 4 project #3. [Lang]
|
||||||
|
|
||||||
|
* Upgrade to the latest symfony v4 #3. [Lang]
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
* Implement CD script to Gitea and add docs to the process #4. [Lang]
|
||||||
|
|
||||||
|
* Remove unnecessary cdn based fonts #4. [Lang]
|
||||||
|
|
||||||
|
* Update docs #4. [Lang]
|
||||||
|
|
||||||
|
* Add JWT generation script to make Mercure safe #4. [Lang]
|
||||||
|
|
||||||
|
* Fix missing favicon #4. [Lang]
|
||||||
|
|
||||||
|
* Make compatible the whole project with bare metal AND with docker #4. [Lang]
|
||||||
|
|
||||||
|
* Add modern Webauthn authentication #4. [Lang]
|
||||||
|
|
||||||
|
* Refactor all forms to have Symfony Form Types & Validation Constrainsts - & implement Google ReCapthca v3 #4. [Lang]
|
||||||
|
|
||||||
|
* Add forgot password functionality #4. [Lang]
|
||||||
|
|
||||||
|
* Increase the minimum PHP version to the latest major - and massive refactor on back-end, like Controllers and Repositories #4. [Lang]
|
||||||
|
|
||||||
|
* Redesign the resign dialog #4. [Lang]
|
||||||
|
|
||||||
|
* Re-implement the waiting for opponent dialog - refactor its gfx - & add online user selection dialog #4. [Lang]
|
||||||
|
|
||||||
|
* Improve the gfx on homepage - implement login/register and activation for authentication - and add the first version of profile page #4. [Lang]
|
||||||
|
|
||||||
|
* Refactor and redesign the gfx on front-end #4. [Lang]
|
||||||
|
|
||||||
|
* Upgrade to the latest LTS Symfony package and backend #4. [Lang]
|
||||||
|
|
||||||
|
* Add timers to each player - renew the whole migration #4. [Lang]
|
||||||
|
|
||||||
|
* Update the vite related stuff because CORS and React errors - reinit the miration #4. [Lang]
|
||||||
|
|
||||||
|
* Use namespaces for front-end #4. [Lang]
|
||||||
|
|
||||||
|
* Replace webpack w/ vite & remove old, legacy jQuery from the code #4. [Lang]
|
||||||
|
|
||||||
|
* More, massive refactor for front-end #4. [Lang]
|
||||||
|
|
||||||
|
* Massive refactor on front-end - and remove unnecessary deps #4. [Lang]
|
||||||
|
|
||||||
|
* Change the code style to fit the current standard #4. [Lang]
|
||||||
|
|
||||||
|
* Refactor to use Attributes instead of yaml markdown #4. [Lang]
|
||||||
|
|
||||||
|
* Outsource the Grid generation and interactions to the backend #4. [Lang]
|
||||||
|
|
||||||
|
* Remove unnecessary variables and prune the Facebook registration method #4. [Lang]
|
||||||
|
|
||||||
|
* Replace the legacy gos/web-socket-bundle & replace it with Mercure protocol #4. [Lang]
|
||||||
|
|
||||||
|
* Make a massive refactor to the backend and remove all unnecessary deps - and make small refactors for the frontend too #4. [Lang]
|
||||||
|
|
||||||
|
* Created the first working solution since 7 yrs #4. [Lang]
|
||||||
|
|
||||||
|
* Add some changes on BE - add eslint and editorconfig - and add some deps #4. [Lang]
|
||||||
|
|
||||||
|
* Make the first working version - the stepping is broken due to the algorythm structure #4. [Lang]
|
||||||
|
|
||||||
|
* Change the composer default php minimum environment #3. [Lang]
|
||||||
|
|
||||||
|
* Change the default url to wss on frontend #3. [Lang]
|
||||||
|
|
||||||
|
* Refactor Rpc and Topic classes #3. [Lang]
|
||||||
|
|
||||||
|
* Refactor classes and reformat some layout #3. [Lang]
|
||||||
|
|
||||||
|
* Remove deprecated files #3. [Lang]
|
||||||
|
|
||||||
|
* Doc in README.md #3. [Lang]
|
||||||
|
|
||||||
|
* Gitignore a js.map file #2. [Lang]
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
* Pkg: usr: solve the not-working mailing on dev env under docker #4. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## 1.1.0 (2019-10-26)
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
* Reinit project - disable redis module and make the project compatible w/ PHP7.3 #2. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.0 (2019-10-26)
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
* Change session driver to REDIS. [Lang]
|
||||||
|
|
||||||
|
* Add created, updated field to db && improve graph design. [Lang]
|
||||||
|
|
||||||
|
* Cache setup && optimalize for google pagespeed && optimalize all images. [Lang]
|
||||||
|
|
||||||
|
* Improve graph design on homepage && add footer and techs && add official pages. [Lang]
|
||||||
|
|
||||||
|
* Bugfix mine websocket periodic mysql calling. [Lang]
|
||||||
|
|
||||||
|
* Bugfix hwioauth remember me && centralize hwioauth and facebook settings. [Lang]
|
||||||
|
|
||||||
|
* Centralize jquery && bugfix mysql auto-termination problem w/ user auth. [Lang]
|
||||||
|
|
||||||
|
* Release beta4. [Lang]
|
||||||
|
|
||||||
|
* Gitignore npm debug log. [Lang]
|
||||||
|
|
||||||
|
* Add english lang everywhere && add snowfall && add centralized version nbr && improve stylesheet && slack integration. [Lang]
|
||||||
|
|
||||||
|
* Bugfix #30 && random bg in game. [Lang]
|
||||||
|
|
||||||
|
* Add google analytics and facebook scripts && improve url share method w/ fb && enforce https in prod. [Lang]
|
||||||
|
|
||||||
|
* Reg and login buttons on index && remove list method && facebook centralize. [Lang]
|
||||||
|
|
||||||
|
* Redesign user frontend. [Lang]
|
||||||
|
|
||||||
|
* Mods for performance; one js.min file on prod. [Lang]
|
||||||
|
|
||||||
|
* Improve webpack config for prod compile #23. [Lang]
|
||||||
|
|
||||||
|
* Ssl handling #22 && reconnection issues #20, #21. [Lang]
|
||||||
|
|
||||||
|
* Facebook prod settings w/ app; hwi/HWIOAuthBundle. [Lang]
|
||||||
|
|
||||||
|
* Refact && game reconnection and restore w/o refresh #3 && bugfix bomb explosion on opponent mines #19. [Lang]
|
||||||
|
|
||||||
|
* Typo in rpc. [Lang]
|
||||||
|
|
||||||
|
* Handle prod mysql timeout && graphics improve. [Lang]
|
||||||
|
|
||||||
|
* Gitignore webpacked index.js. [Lang]
|
||||||
|
|
||||||
|
* Add production mods. [Lang]
|
||||||
|
|
||||||
|
* Bugfix points saving and exploded bombs to db && you can resign #6. [Lang]
|
||||||
|
|
||||||
|
* Bugfix resign button existence #11. [Lang]
|
||||||
|
|
||||||
|
* Bugfix opponent bomb btn buzz on hover #10. [Lang]
|
||||||
|
|
||||||
|
* Bugfix points problem in the end #16. [Lang]
|
||||||
|
|
||||||
|
* Add desc to every user #9. [Lang]
|
||||||
|
|
||||||
|
* Clipboard - not working #8. [Lang]
|
||||||
|
|
||||||
|
* Random player on start #5. [Lang]
|
||||||
|
|
||||||
|
* Show left mines after end #2 && reduce network traffic && better active field checking method. [Lang]
|
||||||
|
|
||||||
|
* Some refactor #13. [Lang]
|
||||||
|
|
||||||
|
* Bugfix grid field render #12. [Lang]
|
||||||
|
|
||||||
|
* Game ends after x mines. [Lang]
|
||||||
|
|
||||||
|
* Add new sounds && refactor && new bg images && form redesigns. [Lang]
|
||||||
|
|
||||||
|
* Bugfix entities gridrow, grid && improve graph design on homepage. [Lang]
|
||||||
|
|
||||||
|
* Some refactor && prod settings. [Lang]
|
||||||
|
|
||||||
|
* Improve graphics design in game. [Lang]
|
||||||
|
|
||||||
|
* Bugfix grid row in entity. [Lang]
|
||||||
|
|
||||||
|
* Bugfix changePlayer after bomb explosion. [Lang]
|
||||||
|
|
||||||
|
* Improve game graph design. [Lang]
|
||||||
|
|
||||||
|
* Login and register form more design. [Lang]
|
||||||
|
|
||||||
|
* Add basic design to userbundle && refactor. [Lang]
|
||||||
|
|
||||||
|
* Add font-awesome. [Lang]
|
||||||
|
|
||||||
|
* Working user authentication w/ fb and plain login. [Lang]
|
||||||
|
|
||||||
|
* Add facebook login module, hwi/HWIOAuthBundle. [Lang]
|
||||||
|
|
||||||
|
* Login && register form overrided. [Lang]
|
||||||
|
|
||||||
|
* Js and config refactor. [Lang]
|
||||||
|
|
||||||
|
* Replace gridcol object to json array in db. [Lang]
|
||||||
|
|
||||||
|
* Refactor. [Lang]
|
||||||
|
|
||||||
|
* Save steps and point info to db. [Lang]
|
||||||
|
|
||||||
|
* Save the step data to db. [Lang]
|
||||||
|
|
||||||
|
* Renamed the acme to mineseeker && handle when the user connection has been lost. [Lang]
|
||||||
|
|
||||||
|
* Add player names to UI. [Lang]
|
||||||
|
|
||||||
|
* Add overlay && game do not start until the opponent came. [Lang]
|
||||||
|
|
||||||
|
* Add base64 encryption to grid when it has been sended to server. [Lang]
|
||||||
|
|
||||||
|
* On click opponents bomb, you cannot target && refactor. [Lang]
|
||||||
|
|
||||||
|
* Warning when player has been found more than 20 mines. [Lang]
|
||||||
|
|
||||||
|
* Bugfix center mine counter animation. [Lang]
|
||||||
|
|
||||||
|
* The opponent is the next when bomb is exploded. [Lang]
|
||||||
|
|
||||||
|
* Current username checked && refactor && remove players in channel when they are more than 2. [Lang]
|
||||||
|
|
||||||
|
* Send bomb info and use it on opponent. [Lang]
|
||||||
|
|
||||||
|
* Add sounds w/ howler. [Lang]
|
||||||
|
|
||||||
|
* Bugfix multiple empty fields w/ one click on opponent view. [Lang]
|
||||||
|
|
||||||
|
* Refact && remove sound and logging && bugfix BIGBUG - handleGridField and showAppropriateFields sort order... [Lang]
|
||||||
|
|
||||||
|
* Create first working communication. [Lang]
|
||||||
|
|
||||||
|
* Create entities and repositories. [Lang]
|
||||||
|
|
||||||
|
* Changed websocket default port && debug RPC. [Lang]
|
||||||
|
|
||||||
|
* Created working session and client handler w/ websocket. [Lang]
|
||||||
|
|
||||||
|
* Working websocket client and server w/o session handling and storage. [Lang]
|
||||||
|
|
||||||
|
* Composer update. [Lang]
|
||||||
|
|
||||||
|
* Improve game && start sound creating. [Lang]
|
||||||
|
|
||||||
|
* Refactor grid control and grid field. [Lang]
|
||||||
|
|
||||||
|
* Created basic game w/ table and animations. [Lang]
|
||||||
|
|
||||||
|
* Websocket basic setup FE & BE && working basic game w/ react && webpack & babel config. [Lang]
|
||||||
|
|
||||||
|
* Gitignore node_modules && add symlink to node_modules (just for install) && basic react. [Lang]
|
||||||
|
|
||||||
|
* Add react hello world. [Lang]
|
||||||
|
|
||||||
|
* Rename project in config. [Lang]
|
||||||
|
|
||||||
|
* Initial commit && create project in symfony3. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,15 @@
|
|||||||
|
|
||||||
encode zstd br gzip
|
encode zstd br gzip
|
||||||
|
|
||||||
|
# Forward scheme information to the PHP application
|
||||||
|
header X-Forwarded-Proto {scheme}
|
||||||
|
header X-Forwarded-Host {host}
|
||||||
|
|
||||||
mercure {
|
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
|
publisher_jwt {$MERCURE_JWT_SECRET} HS256
|
||||||
subscriber_jwt {$MERCURE_JWT_SECRET} HS256
|
subscriber_jwt {$MERCURE_JWT_SECRET} HS256
|
||||||
anonymous
|
anonymous
|
||||||
|
|||||||
+1
-2
@@ -22,9 +22,8 @@ RUN install-php-extensions \
|
|||||||
apcu \
|
apcu \
|
||||||
sodium
|
sodium
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends fonts-dejavu-core && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
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' \
|
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"
|
> "$PHP_INI_DIR/conf.d/opcache.ini"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2026 SplendidBear (https://www.splendidbear.org)
|
||||||
|
|
||||||
|
MineSeeker is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
MineSeeker is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS:
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of works.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this License.
|
||||||
|
|
||||||
|
"You" refers to each licensee.
|
||||||
|
|
||||||
|
"Legal entities" means the union of the acting entity and all other entities
|
||||||
|
that control, are controlled by, or are under common control with that entity.
|
||||||
|
|
||||||
|
"Modify" means to copy from or adapt all or part of the work in a fashion
|
||||||
|
requiring copyright permission, other than the making of an exact copy.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work for making
|
||||||
|
modifications to it. "Object code" means any non-source form of a work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of copyright
|
||||||
|
on the Program. You are granted all permissions necessary to run, modify and
|
||||||
|
propagate covered works by this License.
|
||||||
|
|
||||||
|
3. Copyleft - Derivative Works.
|
||||||
|
|
||||||
|
If you modify the Program, your modified version must:
|
||||||
|
|
||||||
|
- Carry prominent notices stating that you have modified it
|
||||||
|
- License the entire work under this License or a compatible license
|
||||||
|
- Make the source code available to recipients
|
||||||
|
- Preserve all notices of previous licensing
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you receive it,
|
||||||
|
provided that you:
|
||||||
|
|
||||||
|
- Keep intact all notices of authorship and licensing
|
||||||
|
- Give recipients access to the source code along with this License
|
||||||
|
- Do not modify anything except the License itself
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program under this License provided that:
|
||||||
|
|
||||||
|
- The work must be licensed as a whole under this License
|
||||||
|
- You must give prominent notice of any modifications
|
||||||
|
- You must provide access to the Corresponding Source code
|
||||||
|
- You preserve all licensing notices
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
If you convey object code or compiled versions, you must also provide:
|
||||||
|
|
||||||
|
- The Corresponding Source code (in machine-readable form)
|
||||||
|
- A notice of the terms under which it is licensed
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
No additional restrictions may be placed on the exercise of the rights
|
||||||
|
granted or affirmed under this License.
|
||||||
|
|
||||||
|
8. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
9. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING
|
||||||
|
ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF
|
||||||
|
THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS
|
||||||
|
OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR
|
||||||
|
THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
For the complete GPL-3.0-or-later license text, visit:
|
||||||
|
https://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
|
For more information about GNU GPL, visit:
|
||||||
|
https://www.gnu.org/licenses/
|
||||||
|
|
||||||
|
MineSeeker is a multiplayer minesweeper game inspired by MSN Messenger's game.
|
||||||
|
Project: https://www.mineseeker.hu
|
||||||
|
Author: SplendidBear (https://www.splendidbear.org)
|
||||||
|
|
||||||
|
|
||||||
@@ -1,33 +1,59 @@
|
|||||||
.PHONY: help start start-build stop build down ps logs prune-everything db-reset mercure-jwt
|
.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
|
.DEFAULT_GOAL := help
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "Available commands:"
|
@echo "Available commands:"
|
||||||
@echo " make start - Start services without building (uses existing images)"
|
@echo " make start - Start services (storage migration runs automatically once)"
|
||||||
@echo " make start-build - Start services and build images if needed"
|
@echo " make start-build - Build and start services (storage migration runs automatically once)"
|
||||||
@echo " make stop - Stop running services"
|
@echo " make stop - Stop running services"
|
||||||
@echo " make build - Build Docker images only"
|
@echo " make build - Build Docker images only"
|
||||||
@echo " make down - Stop and remove containers/networks"
|
@echo " make down - Stop and remove containers/networks"
|
||||||
@echo " make prune-everything - Prune volumes, networks and images (DANGEROUS!)"
|
@echo " make prune-everything - Prune volumes, networks and images (DANGEROUS!)"
|
||||||
@echo " make db-reset - Reset the database (drop, create, migrate) (DANGEROUS!)"
|
@echo " make db-reset - Reset the database (drop, create, migrate) (DANGEROUS!)"
|
||||||
|
@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:
|
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:
|
start-build:
|
||||||
composer i
|
composer i
|
||||||
bun run build
|
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:
|
stop:
|
||||||
docker compose stop
|
docker compose --profile storage-migration stop
|
||||||
|
|
||||||
build:
|
build:
|
||||||
docker compose build
|
docker compose --profile storage-migration build
|
||||||
|
|
||||||
down:
|
down:
|
||||||
docker compose down
|
docker compose --profile storage-migration down
|
||||||
|
|
||||||
prune-everything:
|
prune-everything:
|
||||||
@echo "WARNING: This will remove ALL containers, volumes, networks and images!"
|
@echo "WARNING: This will remove ALL containers, volumes, networks and images!"
|
||||||
@@ -36,7 +62,7 @@ prune-everything:
|
|||||||
echo "Aborted."; \
|
echo "Aborted."; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
docker compose down -v --rmi all --remove-orphans
|
docker compose --profile storage-migration down -v --rmi all --remove-orphans
|
||||||
|
|
||||||
mercure-jwt:
|
mercure-jwt:
|
||||||
@php bin/generate-mercure-jwt.php
|
@php bin/generate-mercure-jwt.php
|
||||||
@@ -51,3 +77,49 @@ db-reset:
|
|||||||
bin/console doctrine:database:drop --force --if-exists --no-interaction
|
bin/console doctrine:database:drop --force --if-exists --no-interaction
|
||||||
bin/console doctrine:database:create --if-not-exists --no-interaction
|
bin/console doctrine:database:create --if-not-exists --no-interaction
|
||||||
bin/console doctrine:migrations:migrate --no-interaction
|
bin/console doctrine:migrations:migrate --no-interaction
|
||||||
|
|
||||||
|
ccp:
|
||||||
|
bin/console cache:clear --no-warmup --env=prod
|
||||||
|
|
||||||
|
cache-clear:
|
||||||
|
@echo "Clearing all caches..."
|
||||||
|
@rm -rf node_modules/.vite
|
||||||
|
@rm -rf .vite
|
||||||
|
@rm -rf var/og-cache
|
||||||
|
@php bin/console cache:clear --no-warmup
|
||||||
|
@echo "✓ Vite cache cleared"
|
||||||
|
@echo "✓ OG cache cleared"
|
||||||
|
@echo "✓ Symfony cache cleared"
|
||||||
|
@echo ""
|
||||||
|
@echo "Rebuilding assets..."
|
||||||
|
@bun run build
|
||||||
|
@echo ""
|
||||||
|
@echo "✓ All caches cleared and assets rebuilt!"
|
||||||
|
@echo " Next step: Refresh browser with Ctrl+Shift+R"
|
||||||
|
|
||||||
|
og-cache-clear:
|
||||||
|
@echo "Clearing Open Graph cache..."
|
||||||
|
@rm -rf var/og-cache
|
||||||
|
@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
|
- **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
|
- **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
|
- **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
|
- **Battle replay sharing** — share a direct link to any finished game
|
||||||
- **Docker-ready** — single `make start-build` brings up the full production-like stack
|
- **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 |
|
| Frontend | React 19, Vite, MUI, SCSS |
|
||||||
| Database | PostgreSQL 18 |
|
| Database | PostgreSQL 18 |
|
||||||
| Real-time | Mercure (built into FrankenPHP / Caddy) |
|
| 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 |
|
| Image processing | LiipImagine + Flysystem |
|
||||||
| Server | FrankenPHP (Caddy + PHP in one binary) |
|
| Server | FrankenPHP (Caddy + PHP in one binary) |
|
||||||
| Auth | Symfony Security, Scheb 2FA, web-auth/webauthn-framework |
|
| Auth | Symfony Security, Scheb 2FA, web-auth/webauthn-framework |
|
||||||
@@ -44,7 +44,7 @@ Created by [SplendidBear](https://www.splendidbear.org).
|
|||||||
- PostgreSQL 18
|
- PostgreSQL 18
|
||||||
- Caddy with FrankenPHP and the Mercure module
|
- Caddy with FrankenPHP and the Mercure module
|
||||||
- MailHog (or any SMTP server on port 1025)
|
- 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
|
||||||
- Docker Engine 24+
|
- 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` |
|
| `APP_SECRET` | Random 32-byte hex: `openssl rand -hex 32` |
|
||||||
| `POSTGRES_USER/PASSWORD/DB` | Your PostgreSQL credentials |
|
| `POSTGRES_USER/PASSWORD/DB` | Your PostgreSQL credentials |
|
||||||
| `MINIO_ROOT_USER/PASSWORD` | MinIO admin credentials |
|
| `MINIO_ROOT_USER/PASSWORD` | S3 storage credentials (used by RustFS and legacy MinIO during migration) |
|
||||||
| `MINIO_ENDPOINT` | `http://localhost:9000` (bare-metal) |
|
| `MINIO_ENDPOINT` | `http://localhost:9010` (bare-metal) |
|
||||||
| `MINIO_PUBLIC_URL` | Public URL browsers use to reach MinIO |
|
| `MINIO_PUBLIC_URL` | Public URL browsers use to reach RustFS (including media and LiipImagine thumbnails) |
|
||||||
| `RECAPTCHA_SITE_KEY/SECRET_KEY` | Google reCAPTCHA v3 keys for your domain |
|
| `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_SECRET` | Random secret (generated in step 3) |
|
||||||
| `MERCURE_JWT_TOKEN` | Signed publisher JWT (generated in step 3) |
|
| `MERCURE_JWT_TOKEN` | Signed publisher JWT (generated in step 3) |
|
||||||
| `MERCURE_SUBSCRIBER_JWT` | Signed subscriber 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
|
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`).
|
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
|
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:
|
**Bare-metal** — run once after MinIO is up:
|
||||||
|
|
||||||
```bash
|
```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
|
mc mb local/mineseeker
|
||||||
echo '' | mc pipe local/mineseeker/media/.keep
|
echo '' | mc pipe local/mineseeker/media/.keep
|
||||||
echo '' | mc pipe local/mineseeker/cache/.keep
|
echo '' | mc pipe local/mineseeker/cache/.keep
|
||||||
@@ -151,6 +152,7 @@ services:
|
|||||||
app:
|
app:
|
||||||
environment:
|
environment:
|
||||||
MAILER_DSN: smtp://mail:1025?verify_peer=0
|
MAILER_DSN: smtp://mail:1025?verify_peer=0
|
||||||
|
TRUSTED_PROXIES: "0.0.0.0/0"
|
||||||
mail:
|
mail:
|
||||||
image: mailhog/mailhog:latest
|
image: mailhog/mailhog:latest
|
||||||
ports:
|
ports:
|
||||||
@@ -215,14 +217,14 @@ POSTGRES_VERSION=18
|
|||||||
|
|
||||||
MINIO_ROOT_USER=mineseeker
|
MINIO_ROOT_USER=mineseeker
|
||||||
MINIO_ROOT_PASSWORD="<strong password>"
|
MINIO_ROOT_PASSWORD="<strong password>"
|
||||||
MINIO_ENDPOINT=http://minio:9000
|
MINIO_ENDPOINT=http://rustfs:9000
|
||||||
MINIO_PUBLIC_URL=https://aws.mineseeker.hu
|
MINIO_PUBLIC_URL=https://aws.mineseeker.hu
|
||||||
|
|
||||||
MAILER_DSN=smtp://mail:25?verify_peer=0
|
MAILER_DSN=smtp://mail:25?verify_peer=0
|
||||||
MAIL_DOMAIN=mineseeker.hu
|
MAIL_DOMAIN=mineseeker.hu
|
||||||
|
|
||||||
RECAPTCHA_SITE_KEY="<your reCAPTCHA v3 site key>"
|
CAP_API_ENDPOINT="https://cap.example.com/your-site-key/"
|
||||||
RECAPTCHA_SECRET_KEY="<your reCAPTCHA v3 secret key>"
|
CAP_SECRET_KEY="<secret key from your Cap site dashboard>"
|
||||||
|
|
||||||
MERCURE_URL=https://mineseeker.hu/.well-known/mercure
|
MERCURE_URL=https://mineseeker.hu/.well-known/mercure
|
||||||
MERCURE_PUBLIC_URL=https://mineseeker.hu/.well-known/mercure
|
MERCURE_PUBLIC_URL=https://mineseeker.hu/.well-known/mercure
|
||||||
@@ -233,8 +235,13 @@ MERCURE_SUBSCRIBER_JWT="<generated by make mercure-jwt>"
|
|||||||
APP_PUBLIC_HOSTNAME=mineseeker.hu
|
APP_PUBLIC_HOSTNAME=mineseeker.hu
|
||||||
WEBAUTHN_RP_ID=mineseeker.hu
|
WEBAUTHN_RP_ID=mineseeker.hu
|
||||||
WEBAUTHN_ORIGIN=https://mineseeker.hu
|
WEBAUTHN_ORIGIN=https://mineseeker.hu
|
||||||
```
|
|
||||||
|
|
||||||
|
# OG Tags & Social Media Sharing (IMPORTANT for Docker deployments)
|
||||||
|
# TRUSTED_PROXIES: IP address (or range) of your reverse proxy (Caddy/Nginx)
|
||||||
|
# This ensures OG image tags use HTTPS URLs instead of HTTP
|
||||||
|
TRUSTED_PROXIES="172.18.0.0/16"
|
||||||
|
TRUSTED_HOSTS="mineseeker.hu,www.mineseeker.hu"
|
||||||
|
```
|
||||||
### Production server: one-time setup
|
### Production server: one-time setup
|
||||||
|
|
||||||
The server needs Docker, Git, and a self-hosted `act_runner` registered against the Gitea repository. Bun and Composer run inside the multi-stage Dockerfile, so they are not needed on the server.
|
The server needs Docker, Git, and a self-hosted `act_runner` registered against the Gitea repository. Bun and Composer run inside the multi-stage Dockerfile, so they are not needed on the server.
|
||||||
@@ -254,7 +261,7 @@ make mercure-jwt
|
|||||||
|
|
||||||
Copy the three printed values into the `PROD_ENV_FILE` secret.
|
Copy the three printed values into the `PROD_ENV_FILE` secret.
|
||||||
|
|
||||||
#### 5. First deploy
|
#### 3. First deploy
|
||||||
|
|
||||||
Trigger it by pushing the first tag:
|
Trigger it by pushing the first tag:
|
||||||
|
|
||||||
@@ -263,9 +270,9 @@ git tag v2026.01
|
|||||||
git push origin 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.
|
||||||
|
|
||||||
#### 6. Verify
|
#### 4. Verify
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose ps # all services should be healthy/running
|
docker compose ps # all services should be healthy/running
|
||||||
@@ -281,8 +288,93 @@ git push origin v2026.01
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
LGPL-3.0 — see [LICENSE](LICENSE) for details.
|
LGPL-3.0 — see [LICENSE](LICENSE) for details.
|
||||||
|
|
||||||
© 2026 [SplendidBear](https://www.splendidbear.org)
|
© 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
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
@@ -7,9 +7,4 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$font-path: "/build/webfonts";
|
@import '@fortawesome/fontawesome-free/css/all.min.css';
|
||||||
|
|
||||||
@import '@fortawesome/fontawesome-free/scss/fontawesome';
|
|
||||||
@import '@fortawesome/fontawesome-free/scss/brands';
|
|
||||||
@import '@fortawesome/fontawesome-free/scss/solid';
|
|
||||||
@import '@fortawesome/fontawesome-free/scss/regular';
|
|
||||||
|
|||||||
@@ -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 {
|
@keyframes appear {
|
||||||
from { opacity: 0; transform: scale(0.94); }
|
from { opacity: 0; transform: scale(0.94); }
|
||||||
to { opacity: 1; transform: scale(1); }
|
to { opacity: 1; transform: scale(1); }
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
|
/*!*
|
||||||
|
* 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;
|
||||||
|
|
||||||
.hero-auth {
|
.hero-auth {
|
||||||
position: absolute;
|
|
||||||
top: 28px;
|
|
||||||
right: 36px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
@@ -19,6 +29,13 @@
|
|||||||
i { font-size: 15px; }
|
i { font-size: 15px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1100px) {
|
||||||
|
.hero-auth {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.hero-auth-btn {
|
.hero-auth-btn {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -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 {
|
.auth-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -66,7 +75,7 @@
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding: 44px 48px 40px;
|
padding: 44px 48px 40px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 420px;
|
max-width: 520px;
|
||||||
backdrop-filter: blur(4px);
|
backdrop-filter: blur(4px);
|
||||||
box-shadow: 0 8px 48px rgba(0, 0, 0, 0.5);
|
box-shadow: 0 8px 48px rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
@@ -78,6 +87,65 @@
|
|||||||
margin-bottom: 6px;
|
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 {
|
.auth-sub {
|
||||||
font: 400 14px 'Rajdhani', sans-serif;
|
font: 400 14px 'Rajdhani', sans-serif;
|
||||||
color: rgba(149, 207, 245, 0.6);
|
color: rgba(149, 207, 245, 0.6);
|
||||||
@@ -180,6 +248,41 @@
|
|||||||
input[type="checkbox"] { accent-color: #236f87; }
|
input[type="checkbox"] { accent-color: #236f87; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.auth-checkbox {
|
||||||
|
accent-color: #236f87;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
font: 400 14px 'Rajdhani', sans-serif;
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #95cff5;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: color 180ms;
|
||||||
|
|
||||||
|
&:hover { color: #c5e8ff; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.auth-input {
|
||||||
|
padding: 11px 14px;
|
||||||
|
min-height: 120px;
|
||||||
|
resize: vertical;
|
||||||
|
font-family: 'Rajdhani', sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
.auth-submit {
|
.auth-submit {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -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 {
|
main div.txt {
|
||||||
color: rgba(255, 255, 255, 0.85);
|
color: rgba(255, 255, 255, 0.85);
|
||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
@@ -33,3 +42,11 @@ main div.txt a {
|
|||||||
|
|
||||||
&:hover { color: #c5e8ff; }
|
&:hover { color: #c5e8ff; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main div.txt img {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
main div.txt .img-container {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {
|
.hero-cta {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*!*
|
||||||
|
* 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-donate-text {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
font: 400 12px 'Rajdhani', sans-serif;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
margin-top: 42px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
animation: rise 0.8s 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-donate {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
font: 500 12px 'Rajdhani', sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: rgba(255, 184, 82, 0.75);
|
||||||
|
padding: 6px 14px;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid rgba(255, 184, 82, 0.25);
|
||||||
|
|
||||||
|
background: rgba(255, 140, 30, 0.05);
|
||||||
|
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 1px rgba(255, 140, 30, 0.1),
|
||||||
|
0 0 8px rgba(255, 140, 30, 0.08);
|
||||||
|
|
||||||
|
transition: transform 200ms ease, box-shadow 200ms ease, color 200ms ease, background 200ms ease;
|
||||||
|
animation: rise 0.8s 0.58s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-donate::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: -2px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: rgba(255, 140, 30, 0.15);
|
||||||
|
filter: blur(8px);
|
||||||
|
opacity: 0.1;
|
||||||
|
z-index: -1;
|
||||||
|
transition: opacity 200ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-donate:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
color: rgba(255, 200, 100, 0.9);
|
||||||
|
background: rgba(255, 140, 30, 0.1);
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 1px rgba(255, 140, 30, 0.2),
|
||||||
|
0 0 12px rgba(255, 140, 30, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-donate:hover::before {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-donate:active {
|
||||||
|
transform: translateY(0px);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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 {
|
.feature-block {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 80px 40px;
|
padding: 80px 40px;
|
||||||
@@ -45,33 +54,33 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bar chart — large, centre
|
// Bar chart — large, centre
|
||||||
i.fa-bar-chart {
|
i.fa-chart-bar {
|
||||||
font-size: 140px;
|
font-size: 160px;
|
||||||
color: rgba(35, 111, 135, 0.35);
|
color: rgba(35, 111, 135, 0.5);
|
||||||
filter: drop-shadow(0 0 30px rgba(35, 111, 135, 0.3));
|
filter: drop-shadow(0 0 40px rgba(35, 111, 135, 0.5));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trophy — top right
|
// Trophy — top right
|
||||||
i.fa-trophy {
|
i.fa-trophy {
|
||||||
font-size: 64px;
|
font-size: 80px;
|
||||||
top: 12px;
|
top: 0px;
|
||||||
right: 30px;
|
right: 20px;
|
||||||
color: rgba(246, 125, 82, 0.5);
|
color: rgba(246, 125, 82, 0.7);
|
||||||
filter: drop-shadow(0 0 16px rgba(246, 125, 82, 0.25));
|
filter: drop-shadow(0 0 25px rgba(246, 125, 82, 0.4));
|
||||||
}
|
}
|
||||||
|
|
||||||
// History — bottom left
|
// Clock history — bottom left
|
||||||
i.fa-history {
|
i.fa-clock-rotate-left {
|
||||||
font-size: 52px;
|
font-size: 68px;
|
||||||
bottom: 18px;
|
bottom: 0px;
|
||||||
left: 30px;
|
left: 20px;
|
||||||
color: rgba(149, 207, 245, 0.4);
|
color: rgba(149, 207, 245, 0.65);
|
||||||
filter: drop-shadow(0 0 12px rgba(149, 207, 245, 0.2));
|
filter: drop-shadow(0 0 20px rgba(149, 207, 245, 0.35));
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover i.fa-bar-chart { color: rgba(35, 111, 135, 0.6); }
|
&:hover i.fa-chart-bar { color: rgba(35, 111, 135, 0.8); filter: drop-shadow(0 0 50px rgba(35, 111, 135, 0.7)); }
|
||||||
&:hover i.fa-trophy { color: rgba(246, 125, 82, 0.75); }
|
&:hover i.fa-trophy { color: rgba(246, 125, 82, 0.9); filter: drop-shadow(0 0 35px rgba(246, 125, 82, 0.6)); }
|
||||||
&:hover i.fa-history { color: rgba(149, 207, 245, 0.65); }
|
&:hover i.fa-clock-rotate-left { color: rgba(149, 207, 245, 0.9); filter: drop-shadow(0 0 30px rgba(149, 207, 245, 0.5)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MSN visual
|
// MSN visual
|
||||||
@@ -107,6 +116,98 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Privacy visual
|
||||||
|
.feature-block__visual--privacy {
|
||||||
|
height: 260px;
|
||||||
|
gap: 0;
|
||||||
|
|
||||||
|
i {
|
||||||
|
position: absolute;
|
||||||
|
color: rgba(35, 111, 135, 0.5);
|
||||||
|
transition: color 300ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shield — centre, large
|
||||||
|
i.fa-shield {
|
||||||
|
font-size: 140px;
|
||||||
|
color: rgba(34, 197, 94, 0.3);
|
||||||
|
filter: drop-shadow(0 0 30px rgba(34, 197, 94, 0.25));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock — top right
|
||||||
|
i.fa-lock {
|
||||||
|
font-size: 56px;
|
||||||
|
top: 20px;
|
||||||
|
right: 35px;
|
||||||
|
color: rgba(168, 85, 247, 0.5);
|
||||||
|
filter: drop-shadow(0 0 16px rgba(168, 85, 247, 0.2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eye slash — bottom left
|
||||||
|
i.fa-eye-slash {
|
||||||
|
font-size: 48px;
|
||||||
|
bottom: 28px;
|
||||||
|
left: 40px;
|
||||||
|
color: rgba(59, 130, 246, 0.5);
|
||||||
|
filter: drop-shadow(0 0 12px rgba(59, 130, 246, 0.15));
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover i.fa-shield { color: rgba(34, 197, 94, 0.6); }
|
||||||
|
&:hover i.fa-lock { color: rgba(168, 85, 247, 0.75); }
|
||||||
|
&:hover i.fa-eye-slash { color: rgba(59, 130, 246, 0.7); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Practice visual
|
||||||
|
.feature-block__visual--practice {
|
||||||
|
height: 260px;
|
||||||
|
gap: 0;
|
||||||
|
|
||||||
|
i {
|
||||||
|
position: absolute;
|
||||||
|
color: rgba(35, 111, 135, 0.5);
|
||||||
|
transition: color 300ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Laptop — centre, large
|
||||||
|
i.fa-laptop {
|
||||||
|
font-size: 140px;
|
||||||
|
color: rgba(251, 146, 60, 0.3);
|
||||||
|
filter: drop-shadow(0 0 30px rgba(251, 146, 60, 0.25));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linux — top left
|
||||||
|
i.fa-linux {
|
||||||
|
font-size: 48px;
|
||||||
|
top: 20px;
|
||||||
|
left: 35px;
|
||||||
|
color: rgba(245, 158, 11, 0.5);
|
||||||
|
filter: drop-shadow(0 0 16px rgba(245, 158, 11, 0.2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apple — top right
|
||||||
|
i.fa-apple {
|
||||||
|
font-size: 56px;
|
||||||
|
top: 20px;
|
||||||
|
right: 35px;
|
||||||
|
color: rgba(156, 163, 175, 0.5);
|
||||||
|
filter: drop-shadow(0 0 16px rgba(156, 163, 175, 0.2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows — bottom left
|
||||||
|
i.fa-windows {
|
||||||
|
font-size: 48px;
|
||||||
|
bottom: 28px;
|
||||||
|
left: 40px;
|
||||||
|
color: rgba(59, 130, 246, 0.5);
|
||||||
|
filter: drop-shadow(0 0 12px rgba(59, 130, 246, 0.15));
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover i.fa-laptop { color: rgba(251, 146, 60, 0.6); }
|
||||||
|
&:hover i.fa-linux { color: rgba(245, 158, 11, 0.75); }
|
||||||
|
&:hover i.fa-apple { color: rgba(156, 163, 175, 0.75); }
|
||||||
|
&:hover i.fa-windows { color: rgba(59, 130, 246, 0.7); }
|
||||||
|
}
|
||||||
|
|
||||||
// Text side
|
// Text side
|
||||||
.feature-block__text {
|
.feature-block__text {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -161,6 +262,56 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.practice-links {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.practice-link {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
font: 700 13px 'Rajdhani', sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: rgba(149, 207, 245, 0.85);
|
||||||
|
border: 1px solid rgba(59, 130, 246, 0.3);
|
||||||
|
background: rgba(59, 130, 246, 0.08);
|
||||||
|
padding: 10px 18px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 200ms ease;
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(59, 130, 246, 0.2);
|
||||||
|
border-color: rgba(59, 130, 246, 0.6);
|
||||||
|
color: #fff;
|
||||||
|
transform: translateX(4px);
|
||||||
|
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.practice-link-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
object-fit: contain;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
padding: 4px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: all 200ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.practice-link:hover .practice-link-icon {
|
||||||
|
background: rgba(59, 130, 246, 0.15);
|
||||||
|
border-color: rgba(59, 130, 246, 0.4);
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 900px) {
|
@media screen and (max-width: 900px) {
|
||||||
.feature-block__inner,
|
.feature-block__inner,
|
||||||
.feature-block--reverse .feature-block__inner {
|
.feature-block--reverse .feature-block__inner {
|
||||||
@@ -199,4 +350,14 @@
|
|||||||
.feature-block {
|
.feature-block {
|
||||||
padding: 60px 24px;
|
padding: 60px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.practice-links {
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.practice-link {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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 {
|
footer {
|
||||||
background: #040608;
|
background: #040608;
|
||||||
border-top: 1px solid rgba(35, 111, 135, 0.12);
|
border-top: 1px solid rgba(35, 111, 135, 0.12);
|
||||||
@@ -14,7 +23,6 @@ footer {
|
|||||||
gap: 40px;
|
gap: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Left: brand block
|
|
||||||
.footer-brand {
|
.footer-brand {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -55,7 +63,6 @@ footer {
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Right: navigation
|
|
||||||
.footer-nav-label {
|
.footer-nav-label {
|
||||||
font: 700 11px 'Rajdhani', sans-serif;
|
font: 700 11px 'Rajdhani', sans-serif;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@@ -91,7 +98,6 @@ footer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bottom copyright bar
|
|
||||||
.footer-copy {
|
.footer-copy {
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
padding: 16px 60px;
|
padding: 16px 60px;
|
||||||
@@ -113,3 +119,26 @@ footer {
|
|||||||
&:hover { color: #95cff5; }
|
&: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 {
|
header {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
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 {
|
.hero--compact {
|
||||||
min-height: unset;
|
min-height: unset;
|
||||||
padding: 36px 60px 48px;
|
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 {
|
.hero {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|||||||
@@ -28,6 +28,21 @@
|
|||||||
box-shadow: 0 8px 48px rgba(0, 0, 0, 0.4);
|
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 {
|
.profile-avatar {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 80px;
|
width: 80px;
|
||||||
@@ -210,11 +225,43 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&--best {
|
&--bonus {
|
||||||
border-color: rgba(255, 215, 0, 0.15);
|
border-color: rgba(255, 215, 0, 0.18);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: rgba(255, 215, 0, 0.4);
|
border-color: rgba(255, 215, 0, 0.45);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--avg-bonus {
|
||||||
|
border-color: rgba(230, 184, 60, 0.18);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: rgba(230, 184, 60, 0.45);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--chain {
|
||||||
|
border-color: rgba(94, 232, 154, 0.15);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: rgba(94, 232, 154, 0.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--blind {
|
||||||
|
border-color: rgba(255, 140, 90, 0.15);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: rgba(255, 140, 90, 0.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--edge {
|
||||||
|
border-color: rgba(168, 210, 255, 0.15);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: rgba(168, 210, 255, 0.4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,8 +295,24 @@
|
|||||||
color: rgba(80, 200, 220, 0.35);
|
color: rgba(80, 200, 220, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-stat--best & {
|
.profile-stat--bonus & {
|
||||||
color: rgba(255, 215, 0, 0.3);
|
color: rgba(255, 215, 0, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-stat--avg-bonus & {
|
||||||
|
color: rgba(230, 184, 60, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-stat--chain & {
|
||||||
|
color: rgba(94, 232, 154, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-stat--blind & {
|
||||||
|
color: rgba(255, 140, 90, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-stat--edge & {
|
||||||
|
color: rgba(168, 210, 255, 0.3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,9 +352,25 @@
|
|||||||
color: #50c8dc;
|
color: #50c8dc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-stat--best & {
|
.profile-stat--bonus & {
|
||||||
color: #ffd700;
|
color: #ffd700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile-stat--avg-bonus & {
|
||||||
|
color: #e6b83c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-stat--chain & {
|
||||||
|
color: #5ee89a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-stat--blind & {
|
||||||
|
color: #ff8c5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-stat--edge & {
|
||||||
|
color: #a8d2ff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-stat__label {
|
.profile-stat__label {
|
||||||
@@ -363,15 +442,93 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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 {
|
.profile-games {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 6px;
|
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 {
|
.profile-game {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 26px 76px 22px 1fr 18px auto;
|
grid-template-columns: 60px 76px 22px 1fr 18px auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 11px 16px;
|
padding: 11px 16px;
|
||||||
@@ -400,17 +557,31 @@
|
|||||||
&--draw {
|
&--draw {
|
||||||
border-left-color: rgba(149, 207, 245, 0.25);
|
border-left-color: rgba(149, 207, 245, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--ongoing {
|
||||||
|
border-left-color: rgba(255, 193, 7, 0.4);
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-game__badge {
|
.profile-game__badge {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 20px;
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font: 800 10px 'Rajdhani', sans-serif;
|
font: 800 10px 'Rajdhani', sans-serif;
|
||||||
letter-spacing: 0;
|
letter-spacing: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
.profile-game--win & {
|
.profile-game--win & {
|
||||||
background: rgba(42, 158, 96, 0.18);
|
background: rgba(42, 158, 96, 0.18);
|
||||||
@@ -426,12 +597,49 @@
|
|||||||
background: rgba(149, 207, 245, 0.1);
|
background: rgba(149, 207, 245, 0.1);
|
||||||
color: rgba(149, 207, 245, 0.65);
|
color: rgba(149, 207, 245, 0.65);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile-game--ongoing & {
|
||||||
|
background: rgba(255, 193, 7, 0.12);
|
||||||
|
color: #ffc107;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-top-color: #ffc107;
|
||||||
|
border-right-color: #ffc107;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-game--abandoned & {
|
||||||
|
background: rgba(107, 114, 126, 0.18);
|
||||||
|
color: #6b727e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-game__score {
|
.profile-game__score {
|
||||||
font: 700 14px 'Rajdhani', sans-serif;
|
font: 700 14px 'Rajdhani', sans-serif;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-game__vs {
|
.profile-game__vs {
|
||||||
@@ -461,21 +669,23 @@
|
|||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-charts {
|
.profile-charts {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-wrap: wrap;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-chart-block {
|
.profile-chart-block {
|
||||||
flex: 1 1 300px;
|
min-width: 0;
|
||||||
background: rgba(255, 255, 255, 0.03);
|
background: rgba(255, 255, 255, 0.03);
|
||||||
border: 1px solid rgba(35, 111, 135, 0.2);
|
border: 1px solid rgba(35, 111, 135, 0.2);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding: 24px 20px 16px;
|
padding: 24px 20px 16px;
|
||||||
backdrop-filter: blur(4px);
|
|
||||||
box-shadow: 0 8px 48px rgba(0, 0, 0, 0.4);
|
box-shadow: 0 8px 48px rgba(0, 0, 0, 0.4);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -484,12 +694,25 @@
|
|||||||
.profile-section__title {
|
.profile-section__title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--wide {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
|
||||||
|
.profile-chart-inner {
|
||||||
|
justify-content: stretch;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-chart-inner {
|
.profile-chart-inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
overflow: auto;
|
overflow: hidden;
|
||||||
|
|
||||||
svg text {
|
svg text {
|
||||||
font-family: 'Rajdhani', sans-serif !important;
|
font-family: 'Rajdhani', sans-serif !important;
|
||||||
@@ -564,6 +787,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bd-continue {
|
||||||
|
background: linear-gradient(135deg, rgba(42, 158, 96, 0.35) 0%, rgba(94, 232, 154, 0.35) 100%);
|
||||||
|
border: 1px solid rgba(94, 232, 154, 0.6);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #5ee89a;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font: 700 11px 'Rajdhani', sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1.5px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 180ms ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
box-shadow: 0 0 14px rgba(94, 232, 154, 0.25);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: linear-gradient(135deg, rgba(42, 158, 96, 0.55) 0%, rgba(94, 232, 154, 0.55) 100%);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 0 20px rgba(94, 232, 154, 0.45);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.bd-close {
|
.bd-close {
|
||||||
background: rgba(255, 255, 255, 0.05);
|
background: rgba(255, 255, 255, 0.05);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
@@ -747,6 +996,13 @@
|
|||||||
font: 800 24px 'Rajdhani', sans-serif;
|
font: 800 24px 'Rajdhani', sans-serif;
|
||||||
letter-spacing: 2px;
|
letter-spacing: 2px;
|
||||||
|
|
||||||
|
&__img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
&--red {
|
&--red {
|
||||||
background: linear-gradient(135deg, rgba(173, 10, 5, 0.6) 0%, rgba(246, 125, 82, 0.4) 100%);
|
background: linear-gradient(135deg, rgba(173, 10, 5, 0.6) 0%, rgba(246, 125, 82, 0.4) 100%);
|
||||||
border: 2px solid rgba(173, 10, 5, 0.5);
|
border: 2px solid rgba(173, 10, 5, 0.5);
|
||||||
@@ -852,6 +1108,104 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bshare-bonus {
|
||||||
|
padding: 28px 28px 0;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bshare-bonus__title {
|
||||||
|
font: 700 13px 'Rajdhani', sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
color: #ffd700;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
i { font-size: 14px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.bshare-bonus__grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 24px;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bshare-bonus__player {
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
|
||||||
|
&--red {
|
||||||
|
border-color: rgba(246, 125, 82, 0.15);
|
||||||
|
background: rgba(246, 125, 82, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--blue {
|
||||||
|
border-color: rgba(149, 207, 245, 0.15);
|
||||||
|
background: rgba(149, 207, 245, 0.04);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bshare-bonus__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bshare-bonus__points {
|
||||||
|
font: 700 24px 'Rajdhani', sans-serif;
|
||||||
|
background: linear-gradient(135deg, #ffd700, #ffed4e);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bshare-bonus__label {
|
||||||
|
font: 700 11px 'Rajdhani', sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
color: rgba(255, 215, 0, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bshare-bonus__stats {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bshare-bonus__stat {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 12px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bshare-bonus__stat-label {
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
font: 500 11px 'Rajdhani', sans-serif;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bshare-bonus__stat-value {
|
||||||
|
font: 700 13px 'Rajdhani', sans-serif;
|
||||||
|
color: rgba(255, 215, 0, 0.9);
|
||||||
|
min-width: 24px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bshare-bonus__stat--empty {
|
||||||
|
color: rgba(255, 255, 255, 0.4);
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
.bshare-btn {
|
.bshare-btn {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -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;
|
outline: none;
|
||||||
padding: 0;
|
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) {
|
@media screen and (max-width: 900px) {
|
||||||
.hero h1 {
|
.hero h1 {
|
||||||
font-size: 44px;
|
font-size: 44px;
|
||||||
@@ -24,6 +33,10 @@
|
|||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile-charts {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
.profile-header {
|
.profile-header {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -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 {
|
main {
|
||||||
background: #07090d;
|
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;
|
position: fixed;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
left: 20px;
|
left: 20px;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-from-game {
|
||||||
|
display: inline-block;
|
||||||
-ms-transform: scale(1);
|
-ms-transform: scale(1);
|
||||||
-webkit-transform: scale(1);
|
-webkit-transform: scale(1);
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
@@ -22,3 +36,70 @@
|
|||||||
-webkit-transition: all 250ms cubic-bezier(.17, .67, .83, .67);
|
-webkit-transition: all 250ms cubic-bezier(.17, .67, .83, .67);
|
||||||
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 {
|
html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -17,7 +26,6 @@ main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mine-container {
|
.mine-container {
|
||||||
background: url("/images/bg-mineseeker-0-outbg.jpg") no-repeat;
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -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 {
|
#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: -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%);
|
background: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.5) 0%, rgba(125, 185, 232, 0) 100%);
|
||||||
|
|||||||
@@ -0,0 +1,250 @@
|
|||||||
|
/*!*
|
||||||
|
* 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 .bonus-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 58px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
background: #07090d;
|
||||||
|
font-family: 'Rajdhani', sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.25s ease;
|
||||||
|
align-self: stretch;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
filter: brightness(1.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#mine-wrapper .bonus-box.red-bonus {
|
||||||
|
background: linear-gradient(to bottom, #2a0502 0%, #4a1510 100%);
|
||||||
|
border-color: rgba(246, 125, 82, 0.4);
|
||||||
|
color: rgba(246, 125, 82, 0.85);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: rgba(246, 125, 82, 0.85);
|
||||||
|
box-shadow: 0 0 12px rgba(173, 10, 5, 0.6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#mine-wrapper .bonus-box.blue-bonus {
|
||||||
|
background: linear-gradient(to bottom, #050f18 0%, #0f2838 100%);
|
||||||
|
border-color: rgba(149, 207, 245, 0.4);
|
||||||
|
color: rgba(149, 207, 245, 0.85);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: rgba(149, 207, 245, 0.85);
|
||||||
|
box-shadow: 0 0 12px rgba(35, 111, 135, 0.6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#mine-wrapper .bonus-box__icon {
|
||||||
|
font-size: 13px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mine-wrapper .bonus-box__value {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 16px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-family: 'Rajdhani', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 18px 22px 14px;
|
||||||
|
border-bottom: 1px solid rgba(35, 111, 135, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd-header-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd-label {
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
color: rgba(149, 207, 245, 0.7);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
color: #f6d572;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd-close {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid rgba(35, 111, 135, 0.4);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #fff;
|
||||||
|
border-color: rgba(149, 207, 245, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd-body {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 18px 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd-column {
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd-column--red {
|
||||||
|
border-color: rgba(246, 125, 82, 0.35);
|
||||||
|
background: linear-gradient(to bottom, rgba(74, 6, 3, 0.35), rgba(107, 37, 21, 0.15));
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd-column--blue {
|
||||||
|
border-color: rgba(149, 207, 245, 0.35);
|
||||||
|
background: linear-gradient(to bottom, rgba(11, 37, 48, 0.35), rgba(22, 61, 85, 0.15));
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd-column-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd-column-name {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd-column-total {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #f6d572;
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd-stats {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd-stat {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 6px 2px;
|
||||||
|
border-bottom: 1px dashed rgba(255, 255, 255, 0.06);
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd-stat-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd-stat-label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd-stat-desc {
|
||||||
|
font-size: 11px;
|
||||||
|
color: rgba(255, 255, 255, 0.48);
|
||||||
|
line-height: 1.25;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd-stat-value {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
min-width: 24px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsd-note {
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px 22px 18px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(255, 255, 255, 0.45);
|
||||||
|
text-align: center;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
.bsd-body {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {
|
#mine-wrapper .grid {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -78,8 +87,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#mine-wrapper .grid .field-wrapper .field .field-corner {
|
#mine-wrapper .grid .field-wrapper .field .field-corner {
|
||||||
background: url('/images/bg-corner-outbg.png') no-repeat top left;
|
|
||||||
background-size: 100%;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 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 .users .active-mines-container {
|
#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: -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%);
|
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 {
|
#mine-wrapper .game-wrapper .game-overlay {
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -35,6 +44,8 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
animation: slideUp 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
|
animation: slideUp 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: 90vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slideUp {
|
@keyframes slideUp {
|
||||||
@@ -54,6 +65,11 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
margin: 0 0 50px 0;
|
margin: 0 0 50px 0;
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window h2 {
|
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window h2 {
|
||||||
@@ -183,6 +199,10 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
animation: fadeInUp 0.6s ease-out 0.2s both;
|
animation: fadeInUp 0.6s ease-out 0.2s both;
|
||||||
|
|
||||||
|
&.waiting-options--invite-only {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
@@ -264,6 +284,11 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
letter-spacing: 0.4px;
|
letter-spacing: 0.4px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
.waiting-divider {
|
.waiting-divider {
|
||||||
@@ -527,6 +552,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .game-overlay-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
flex: 1 1 0;
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .game-overlay-share {
|
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .game-overlay-share {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -590,3 +628,153 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .game-overlay-profile {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 9px;
|
||||||
|
background: linear-gradient(to bottom, #1a6844 0%, #135233 100%);
|
||||||
|
border: 2px solid #2a9e60;
|
||||||
|
color: #d0ffe0;
|
||||||
|
font-family: 'Rajdhani', sans-serif;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 300ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 20px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 12px rgba(42, 158, 96, 0.25);
|
||||||
|
text-decoration: none;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||||
|
transition: left 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: linear-gradient(to bottom, #238f5c 0%, #1a6844 100%);
|
||||||
|
border-color: #5ee89a;
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 8px 24px rgba(42, 158, 96, 0.4);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaptchaOverlay Styles
|
||||||
|
.captcha-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(7, 9, 13, 0.95);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-content {
|
||||||
|
text-align: center;
|
||||||
|
color: #fff;
|
||||||
|
max-width: 400px;
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-icon {
|
||||||
|
font-size: 64px;
|
||||||
|
color: #236f87;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-title {
|
||||||
|
font: 800 32px 'Rajdhani', sans-serif;
|
||||||
|
margin: 0 0 16px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-description {
|
||||||
|
color: rgba(149, 207, 245, 0.7);
|
||||||
|
font: 400 16px 'Rajdhani', sans-serif;
|
||||||
|
margin: 0 0 32px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-button {
|
||||||
|
background: linear-gradient(#236f87 0%, #1a5068 100%);
|
||||||
|
border: 2px solid #2e7a9a;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #e0f4ff;
|
||||||
|
cursor: pointer;
|
||||||
|
font: 800 18px 'Rajdhani', sans-serif;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
padding: 16px 40px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: wait;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background: linear-gradient(#2d8aa8 0%, #236f87 100%);
|
||||||
|
border-color: #5ba4d4;
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 8px 24px rgba(35, 111, 135, 0.4);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.captcha-button--error {
|
||||||
|
background: linear-gradient(#8a2323 0%, #681a1a 100%);
|
||||||
|
border-color: #9a2e2e;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: linear-gradient(#a82d2d 0%, #872323 100%);
|
||||||
|
border-color: #d45b5b;
|
||||||
|
box-shadow: 0 8px 24px rgba(135, 35, 35, 0.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.captcha-button--loading {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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) {
|
@media screen and (max-width: 900px) {
|
||||||
#mine-wrapper .game-wrapper .users {
|
#mine-wrapper .game-wrapper .users {
|
||||||
visibility: hidden;
|
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 {
|
#mine-wrapper .game-timer-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
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 {
|
#mine-wrapper .game-wrapper .users {
|
||||||
width: 180px;
|
width: 180px;
|
||||||
padding: 0 10px 0 0;
|
padding: 0 10px 0 0;
|
||||||
@@ -105,10 +114,12 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
padding: 3px 0;
|
padding: 3px 5px;
|
||||||
margin: 0 5px;
|
margin: 0;
|
||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
word-break: break-word;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mine-wrapper .game-wrapper .users .user-container.user-blue .user-name {
|
#mine-wrapper .game-wrapper .users .user-container.user-blue .user-name {
|
||||||
@@ -142,6 +153,13 @@
|
|||||||
height: 65px;
|
height: 65px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
word-break: break-word;
|
||||||
|
padding: 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mine-wrapper .game-wrapper .users .user-container.user-blue .user-desc {
|
#mine-wrapper .game-wrapper .users .user-container.user-blue .user-desc {
|
||||||
|
|||||||
@@ -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 {
|
.opd-paper {
|
||||||
background: #07090d !important;
|
background: #07090d !important;
|
||||||
background-image: linear-gradient(rgba(35, 111, 135, 0.08) 1px, transparent 1px),
|
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";
|
@use "sass:color";
|
||||||
|
|
||||||
.twofa-status {
|
.twofa-status {
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
|
/*!*
|
||||||
|
* 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/reset';
|
||||||
@use 'homepage/animations';
|
@use 'homepage/animations';
|
||||||
@use 'homepage/header';
|
@use 'homepage/header';
|
||||||
@use 'homepage/hero';
|
@use 'homepage/hero';
|
||||||
@use 'homepage/hero-compact';
|
@use 'homepage/hero-compact';
|
||||||
@use 'homepage/cta';
|
@use 'homepage/cta';
|
||||||
|
@use 'homepage/donate';
|
||||||
@use 'homepage/auth-bar';
|
@use 'homepage/auth-bar';
|
||||||
@use 'homepage/auth';
|
@use 'homepage/auth';
|
||||||
@use 'homepage/content';
|
@use 'homepage/content';
|
||||||
@@ -11,4 +21,5 @@
|
|||||||
@use 'homepage/tech';
|
@use 'homepage/tech';
|
||||||
@use 'homepage/footer';
|
@use 'homepage/footer';
|
||||||
@use 'homepage/profile';
|
@use 'homepage/profile';
|
||||||
|
@use 'homepage/battle-dialog';
|
||||||
@use 'homepage/responsive';
|
@use 'homepage/responsive';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*!*
|
/*!*
|
||||||
* This file is part of the SplendidBear Websites' projects.
|
* 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
|
* For the full copyright and license information, please view the LICENSE
|
||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
@@ -320,7 +320,6 @@ footer nav ul li {
|
|||||||
}
|
}
|
||||||
|
|
||||||
footer nav ul li:nth-child(even) {
|
footer nav ul li:nth-child(even) {
|
||||||
width: 50px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,8 +400,4 @@ footer nav ul li a:hover {
|
|||||||
footer nav ul li {
|
footer nav ul li {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer nav ul li:nth-child(even) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*!*
|
/*!*
|
||||||
* This file is part of the SplendidBear Websites' projects.
|
* 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
|
* For the full copyright and license information, please view the LICENSE
|
||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
@@ -19,5 +19,6 @@
|
|||||||
@import 'mineseeker/grid';
|
@import 'mineseeker/grid';
|
||||||
@import 'mineseeker/back-button';
|
@import 'mineseeker/back-button';
|
||||||
@import 'mineseeker/timer';
|
@import 'mineseeker/timer';
|
||||||
|
@import 'mineseeker/bonus-box';
|
||||||
@import 'mineseeker/responsive';
|
@import 'mineseeker/responsive';
|
||||||
@import 'mineseeker/waiting-dialog';
|
@import 'mineseeker/waiting-dialog';
|
||||||
|
|||||||
@@ -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 {
|
.mine-beta {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|||||||
Binary file not shown.
@@ -17,5 +17,6 @@ createRoot(wrapper).render(
|
|||||||
<MineSeeker
|
<MineSeeker
|
||||||
env={wrapper.dataset.env}
|
env={wrapper.dataset.env}
|
||||||
gameId={wrapper.dataset.gameId}
|
gameId={wrapper.dataset.gameId}
|
||||||
|
opponentName={wrapper.dataset.opponentName || ''}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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 }) {
|
import React, { Fragment, useMemo, useRef } from 'react';
|
||||||
const [thumbUrl, setThumbUrl] = useState(initialThumbUrl || null);
|
import { string } from 'prop-types';
|
||||||
const [loading, setLoading] = useState(false);
|
import { useProfileDataProvider } from '@mine-hooks/useGameDataProvider';
|
||||||
const [error, setError] = useState(null);
|
|
||||||
|
export const AvatarUpload = ({ uploadUrl, initialThumbUrl, initials }) => {
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
|
const [thumbUrl, setThumbUrl] = React.useState(initialThumbUrl || null);
|
||||||
|
const { uploadAvatarMutation: { isPending, error, mutate } } = useProfileDataProvider();
|
||||||
|
|
||||||
function handleClick() {
|
const errorMessage = useMemo(() => error?.message ?? null, [error]);
|
||||||
inputRef.current?.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleChange(e) {
|
const handleChange = e => {
|
||||||
const file = e.target.files?.[0];
|
const file = e.target.files?.[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
const fd = new FormData();
|
mutate({ uploadUrl, file }, {
|
||||||
fd.append('avatar', file);
|
onSuccess: data => {
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
setError(null);
|
|
||||||
|
|
||||||
fetch(uploadUrl, { method: 'POST', body: fd })
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.error) {
|
|
||||||
setError(data.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setThumbUrl(data.thumbUrl);
|
setThumbUrl(data.thumbUrl);
|
||||||
|
|
||||||
const navImg = document.querySelector('.hero-auth-avatar:not(.hero-auth-avatar--initials)');
|
const navImg = document.querySelector('.hero-auth-avatar:not(.hero-auth-avatar--initials)');
|
||||||
const navInitials = document.querySelector('.hero-auth-avatar.hero-auth-avatar--initials');
|
const navInitials = document.querySelector('.hero-auth-avatar.hero-auth-avatar--initials');
|
||||||
|
|
||||||
if (navImg) {
|
if (navImg) {
|
||||||
navImg.src = data.thumbUrl;
|
navImg.src = data.thumbUrl;
|
||||||
} else if (navInitials) {
|
} else if (navInitials) {
|
||||||
@@ -40,16 +38,16 @@ export default function AvatarUpload({ uploadUrl, initialThumbUrl, initials }) {
|
|||||||
img.className = 'hero-auth-avatar';
|
img.className = 'hero-auth-avatar';
|
||||||
navInitials.replaceWith(img);
|
navInitials.replaceWith(img);
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
.catch(() => setError('Upload failed. Please try again.'))
|
});
|
||||||
.finally(() => setLoading(false));
|
};
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Fragment>
|
||||||
<div
|
<div
|
||||||
className={`profile-avatar${loading ? ' profile-avatar--loading' : ''}`}
|
className={`profile-avatar${isPending ? ' profile-avatar--loading' : ''}`}
|
||||||
title="Click to change profile picture"
|
title="Click to change profile picture"
|
||||||
onClick={handleClick}
|
onClick={() => inputRef.current?.click()}
|
||||||
>
|
>
|
||||||
{thumbUrl
|
{thumbUrl
|
||||||
? <img src={thumbUrl} alt={initials} className="profile-avatar__img" />
|
? <img src={thumbUrl} alt={initials} className="profile-avatar__img" />
|
||||||
@@ -65,7 +63,14 @@ export default function AvatarUpload({ uploadUrl, initialThumbUrl, initials }) {
|
|||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
{error && <div className="profile-avatar__error">{error}</div>}
|
|
||||||
</div>
|
</div>
|
||||||
|
{errorMessage && <div className="profile-avatar__error">{errorMessage}</div>}
|
||||||
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
AvatarUpload.propTypes = {
|
||||||
|
uploadUrl: string.isRequired,
|
||||||
|
initialThumbUrl: string,
|
||||||
|
initials: string.isRequired,
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,31 +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 React, { useEffect, useState } from 'react';
|
||||||
|
import { array } from 'prop-types';
|
||||||
|
import { formatDuration } from '@global-utils/format';
|
||||||
import Dialog from '@mui/material/Dialog';
|
import Dialog from '@mui/material/Dialog';
|
||||||
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
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 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 = {
|
const RESULT_META = {
|
||||||
win: {
|
win: {
|
||||||
label: 'Victory',
|
label: 'Victory',
|
||||||
@@ -35,7 +27,7 @@ const RESULT_META = {
|
|||||||
icon: 'fa-trophy',
|
icon: 'fa-trophy',
|
||||||
},
|
},
|
||||||
loss: {
|
loss: {
|
||||||
label: 'Defeat',
|
label: 'Defeated',
|
||||||
color: '#f67d52',
|
color: '#f67d52',
|
||||||
bg: 'rgba(173,10,5,0.15)',
|
bg: 'rgba(173,10,5,0.15)',
|
||||||
border: 'rgba(173,10,5,0.4)',
|
border: 'rgba(173,10,5,0.4)',
|
||||||
@@ -50,90 +42,7 @@ const RESULT_META = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function Avatar({ name, color }) {
|
export const BattleDialog = ({ games }) => {
|
||||||
const isRed = 'red' === color;
|
|
||||||
const initials = (name || '?').slice(0, 2).toUpperCase();
|
|
||||||
|
|
||||||
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';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10 }}>
|
|
||||||
<div style={{
|
|
||||||
width: 72, height: 72, borderRadius: '50%',
|
|
||||||
background: gradient,
|
|
||||||
border: `2px solid ${border}`,
|
|
||||||
boxShadow: glow,
|
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
||||||
font: '800 24px \'Rajdhani\', sans-serif',
|
|
||||||
color: textColor,
|
|
||||||
letterSpacing: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{initials}
|
|
||||||
</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>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function BattleDialog({ games }) {
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [game, setGame] = useState(null);
|
const [game, setGame] = useState(null);
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
@@ -153,15 +62,26 @@ export default function BattleDialog({ games }) {
|
|||||||
}, [games]);
|
}, [games]);
|
||||||
|
|
||||||
if (!game) {
|
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;
|
const meta = RESULT_META[game.result] ?? RESULT_META.draw;
|
||||||
const resign = game.resign;
|
const resign = game.resign;
|
||||||
|
const maxPoints = Math.max(game.redPoints ?? 0, game.bluePoints ?? 0);
|
||||||
const endReason = resign
|
const endReason = resign
|
||||||
? `${resign.charAt(0).toUpperCase() + resign.slice(1)} resigned`
|
? `${resign.charAt(0).toUpperCase() + resign.slice(1)} resigned`
|
||||||
: 'Points';
|
: 26 <= maxPoints ? 'Points' : 'Abandoned';
|
||||||
|
const bothRegistered = game.bothRegistered;
|
||||||
const shareUrl = `${window.location.origin}/battle/${game.uuid}`;
|
const shareUrl = `${window.location.origin}/battle/${game.uuid}`;
|
||||||
|
const canContinue = bothRegistered && !resign && 26 > maxPoints;
|
||||||
|
const canShare = !canContinue;
|
||||||
|
const playUrl = `${window.location.origin}/play/${game.uuid}`;
|
||||||
|
|
||||||
|
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'
|
||||||
|
: (game.bluePoints ?? 0) > (game.redPoints ?? 0) ? '#95cff5'
|
||||||
|
: 'rgba(255,255,255,0.45)';
|
||||||
|
|
||||||
const handleShare = () => {
|
const handleShare = () => {
|
||||||
navigator.clipboard.writeText(shareUrl).then(() => {
|
navigator.clipboard.writeText(shareUrl).then(() => {
|
||||||
@@ -172,7 +92,7 @@ export default function BattleDialog({ games }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={darkTheme}>
|
<ThemeProvider theme={darkTheme}>
|
||||||
<Dialog open={open} onClose={() => setOpen(false)} sx={DIALOG_SX}>
|
<StyledDialog open={open} onClose={() => setOpen(false)}>
|
||||||
<div className="bd">
|
<div className="bd">
|
||||||
<div className="bd-header">
|
<div className="bd-header">
|
||||||
<div className="bd-header-left">
|
<div className="bd-header-left">
|
||||||
@@ -181,7 +101,18 @@ export default function BattleDialog({ games }) {
|
|||||||
<i className="fa fa-crosshairs" /> Match Details
|
<i className="fa fa-crosshairs" /> Match Details
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 8 }}>
|
<div className="bd-header-actions">
|
||||||
|
{canContinue ? (
|
||||||
|
<a
|
||||||
|
className="bd-continue"
|
||||||
|
href={playUrl}
|
||||||
|
aria-label="Continue the game"
|
||||||
|
title="Continue the game"
|
||||||
|
>
|
||||||
|
<i className="fa fa-play" />
|
||||||
|
Continue
|
||||||
|
</a>
|
||||||
|
) : canShare ? (
|
||||||
<button
|
<button
|
||||||
className={`bd-share${copied ? ' bd-share--copied' : ''}`}
|
className={`bd-share${copied ? ' bd-share--copied' : ''}`}
|
||||||
onClick={handleShare}
|
onClick={handleShare}
|
||||||
@@ -191,48 +122,102 @@ export default function BattleDialog({ games }) {
|
|||||||
<i className={`fa ${copied ? 'fa-check' : 'fa-share-alt'}`} />
|
<i className={`fa ${copied ? 'fa-check' : 'fa-share-alt'}`} />
|
||||||
{copied ? 'Copied!' : 'Share'}
|
{copied ? 'Copied!' : 'Share'}
|
||||||
</button>
|
</button>
|
||||||
|
) : null}
|
||||||
<button className="bd-close" onClick={() => setOpen(false)} aria-label="Close">
|
<button className="bd-close" onClick={() => setOpen(false)} aria-label="Close">
|
||||||
<i className="fa fa-times" />
|
<i className="fa fa-times" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bd-vs-panel">
|
<div className="bd-vs-panel">
|
||||||
<Avatar name={game.redName} color="red" />
|
<Avatar
|
||||||
|
name={game.redName} color="red" avatarUrl={game.redAvatar}
|
||||||
|
bonusPoints={game.redBonusPoints > game.blueBonusPoints ? game.redBonusPoints : 0}
|
||||||
|
/>
|
||||||
<div className="bd-vs-center">
|
<div className="bd-vs-center">
|
||||||
<div className="bd-vs-score">
|
<div className="bd-vs-score">
|
||||||
<span className="bd-vs-score__red">{game.redPoints ?? '—'}</span>
|
<span className="bd-vs-score__red">{game.redPoints ?? '—'}</span>
|
||||||
<span className="bd-vs-score__sep">:</span>
|
<span className="bd-vs-score__sep">:</span>
|
||||||
<span className="bd-vs-score__blue">{game.bluePoints ?? '—'}</span>
|
<span className="bd-vs-score__blue">{game.bluePoints ?? '—'}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<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 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-vs-label">VS</div>
|
||||||
<div
|
<div
|
||||||
className="bd-result-badge"
|
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}
|
<i className={`fa ${meta.icon}`} /> {meta.label}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Avatar name={game.blueName} color="blue" />
|
<Avatar
|
||||||
|
name={game.blueName} color="blue" avatarUrl={game.blueAvatar}
|
||||||
|
bonusPoints={game.blueBonusPoints > game.redBonusPoints ? game.blueBonusPoints : 0}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="bd-stats">
|
<div className="bd-stats">
|
||||||
<StatRow icon="fa-calendar" label="Date" value={game.date ?? '—'} />
|
<StatRow icon="fa-calendar" label="Date" value={game.date ?? '—'} />
|
||||||
|
{game.created && game.date && game.created !== game.date && (
|
||||||
|
<StatRow icon="fa-clock" label="Started" value={game.created} />
|
||||||
|
)}
|
||||||
|
{duration && (
|
||||||
|
<StatRow icon="fa-hourglass-half" label="Match duration" value={duration} />
|
||||||
|
)}
|
||||||
<StatRow icon="fa-flag-checkered" label="End reason" value={endReason} />
|
<StatRow icon="fa-flag-checkered" label="End reason" value={endReason} />
|
||||||
|
{0 < pointDiff && (
|
||||||
<StatRow
|
<StatRow
|
||||||
icon="fa-bomb" label="Red hit a mine"
|
icon="fa-balance-scale" label="Winning margin"
|
||||||
|
value={`${pointDiff} mine${1 === pointDiff ? '' : 's'}`} valueColor={winnerColor}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<StatRow
|
||||||
|
icon="fa-bomb" label="Red used bomb"
|
||||||
value={game.redExplodedBomb ? 'Yes' : 'No'}
|
value={game.redExplodedBomb ? 'Yes' : 'No'}
|
||||||
valueColor={game.redExplodedBomb ? '#f67d52' : 'rgba(255,255,255,0.45)'}
|
valueColor={game.redExplodedBomb ? '#f67d52' : 'rgba(255,255,255,0.45)'}
|
||||||
/>
|
/>
|
||||||
<StatRow
|
<StatRow
|
||||||
icon="fa-bomb" label="Blue hit a mine"
|
icon="fa-bomb" label="Blue used bomb"
|
||||||
value={game.blueExplodedBomb ? 'Yes' : 'No'}
|
value={game.blueExplodedBomb ? 'Yes' : 'No'}
|
||||||
valueColor={game.blueExplodedBomb ? '#f67d52' : 'rgba(255,255,255,0.45)'}
|
valueColor={game.blueExplodedBomb ? '#95cff5' : 'rgba(255,255,255,0.45)'}
|
||||||
/>
|
/>
|
||||||
{game.created && game.date && game.created !== game.date && (
|
|
||||||
<StatRow icon="fa-clock-o" label="Started" value={game.created} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
<BonusPoints
|
||||||
|
game={game}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</StyledDialog>
|
||||||
</ThemeProvider>
|
</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)',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useCallback } from 'react';
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import { shape, string } from 'prop-types';
|
||||||
|
|
||||||
const base64ToArrayBuffer = base64 => {
|
const base64ToArrayBuffer = base64 => {
|
||||||
const binary = atob(base64.replace(/([-_])/g, m => ('-' === m ? '+' : '/')));
|
const binary = atob(base64.replace(/([-_])/g, m => ('-' === m ? '+' : '/')));
|
||||||
@@ -108,3 +109,10 @@ const PasskeyLogin = ({ apiRoutes }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default PasskeyLogin;
|
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 React, { Fragment, useCallback, useEffect, useState } from 'react';
|
||||||
import Dialog from '@mui/material/Dialog';
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import { styled } from '@mui/material/styles';
|
||||||
import DialogTitle from '@mui/material/DialogTitle';
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
import DialogContent from '@mui/material/DialogContent';
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
import DialogActions from '@mui/material/DialogActions';
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
|
import { arrayOf, shape, string, bool } from 'prop-types';
|
||||||
|
|
||||||
const DIALOG_SX = {
|
const StyledDialog = styled(Dialog)({
|
||||||
'& .MuiDialog-paper': {
|
'& .MuiDialog-paper': {
|
||||||
background: '#0a0e14',
|
background: '#0a0e14',
|
||||||
color: '#e0e0e0',
|
color: '#e0e0e0',
|
||||||
@@ -47,7 +49,7 @@ const DIALOG_SX = {
|
|||||||
background: 'rgba(2, 4, 8, 0.88)',
|
background: 'rgba(2, 4, 8, 0.88)',
|
||||||
backdropFilter: 'blur(4px)',
|
backdropFilter: 'blur(4px)',
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
const base64ToArrayBuffer = base64 => {
|
const base64ToArrayBuffer = base64 => {
|
||||||
const binary = atob(base64.replace(/([-_])/g, m => ('-' === m ? '+' : '/')));
|
const binary = atob(base64.replace(/([-_])/g, m => ('-' === m ? '+' : '/')));
|
||||||
@@ -314,7 +316,7 @@ const PasskeyManager = ({ credentials, apiRoutes }) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Dialog open={addModalOpen} onClose={closeAddModal} sx={DIALOG_SX}>
|
<StyledDialog open={addModalOpen} onClose={closeAddModal}>
|
||||||
<DialogTitle>Add New Passkey</DialogTitle>
|
<DialogTitle>Add New Passkey</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -344,9 +346,9 @@ const PasskeyManager = ({ credentials, apiRoutes }) => {
|
|||||||
Continue
|
Continue
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</StyledDialog>
|
||||||
|
|
||||||
<Dialog open={renameModalOpen} onClose={closeRenameModal} sx={DIALOG_SX}>
|
<StyledDialog open={renameModalOpen} onClose={closeRenameModal}>
|
||||||
<DialogTitle>Rename Passkey</DialogTitle>
|
<DialogTitle>Rename Passkey</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -374,9 +376,9 @@ const PasskeyManager = ({ credentials, apiRoutes }) => {
|
|||||||
Rename
|
Rename
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</StyledDialog>
|
||||||
|
|
||||||
<Dialog open={deleteModalOpen} onClose={closeDeleteModal} sx={DIALOG_SX}>
|
<StyledDialog open={deleteModalOpen} onClose={closeDeleteModal}>
|
||||||
<DialogTitle>Delete Passkey</DialogTitle>
|
<DialogTitle>Delete Passkey</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<p>
|
<p>
|
||||||
@@ -402,9 +404,25 @@ const PasskeyManager = ({ credentials, apiRoutes }) => {
|
|||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</StyledDialog>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PasskeyManager;
|
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,7 +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 React from 'react';
|
||||||
import { BarChart } from '@mui/x-charts/BarChart';
|
import { BarChart } from '@mui/x-charts/BarChart';
|
||||||
|
import { LineChart } from '@mui/x-charts/LineChart';
|
||||||
import { PieChart } from '@mui/x-charts/PieChart';
|
import { PieChart } from '@mui/x-charts/PieChart';
|
||||||
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||||||
|
import { shape, arrayOf, number, string } from 'prop-types';
|
||||||
|
|
||||||
const darkTheme = createTheme({
|
const darkTheme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
@@ -16,6 +27,8 @@ const darkTheme = createTheme({
|
|||||||
const WIN_COLOR = '#5ee89a';
|
const WIN_COLOR = '#5ee89a';
|
||||||
const LOSS_COLOR = '#f67d52';
|
const LOSS_COLOR = '#f67d52';
|
||||||
const DRAW_COLOR = '#95cff5';
|
const DRAW_COLOR = '#95cff5';
|
||||||
|
const MINES_COLOR = '#f67d52';
|
||||||
|
const BONUS_COLOR = '#ffd700';
|
||||||
|
|
||||||
const axisStyle = {
|
const axisStyle = {
|
||||||
tickLabelStyle: { fill: 'rgba(255,255,255,0.4)', fontSize: 11, fontFamily: '\'Rajdhani\', sans-serif' },
|
tickLabelStyle: { fill: 'rgba(255,255,255,0.4)', fontSize: 11, fontFamily: '\'Rajdhani\', sans-serif' },
|
||||||
@@ -23,10 +36,12 @@ const axisStyle = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function ProfileCharts({ chartData }) {
|
export default function ProfileCharts({ chartData }) {
|
||||||
const { months, wins, losses, draws, pieWins, pieLosses, pieDraws } = chartData;
|
const { months, wins, losses, draws, pieWins, pieLosses, pieDraws, recentGames } = chartData;
|
||||||
const total = pieWins + pieLosses + pieDraws;
|
const total = pieWins + pieLosses + pieDraws;
|
||||||
|
|
||||||
const hasBars = wins.some(v => 0 < v) || losses.some(v => 0 < v) || draws.some(v => 0 < v);
|
const hasBars = wins.some(v => 0 < v) || losses.some(v => 0 < v) || draws.some(v => 0 < v);
|
||||||
|
const hasRecent = recentGames
|
||||||
|
&& (recentGames.mines?.some(v => 0 < v) || recentGames.bonus?.some(v => 0 < v));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={darkTheme}>
|
<ThemeProvider theme={darkTheme}>
|
||||||
@@ -97,7 +112,54 @@ export default function ProfileCharts({ chartData }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{hasRecent && (
|
||||||
|
<div className="profile-chart-block profile-chart-block--wide">
|
||||||
|
<h2 className="profile-section__title">
|
||||||
|
<i className="fa fa-line-chart" /> Last {recentGames.labels.length} games — mines & bonus
|
||||||
|
</h2>
|
||||||
|
<div className="profile-chart-inner">
|
||||||
|
<LineChart
|
||||||
|
xAxis={[{ scaleType: 'band', data: recentGames.labels, ...axisStyle }]}
|
||||||
|
yAxis={[{ ...axisStyle }]}
|
||||||
|
series={[
|
||||||
|
{ data: recentGames.mines, label: 'Mines hit', color: MINES_COLOR },
|
||||||
|
{ data: recentGames.bonus, label: 'Bonus points', color: BONUS_COLOR },
|
||||||
|
]}
|
||||||
|
slotProps={{
|
||||||
|
legend: {
|
||||||
|
labelStyle: {
|
||||||
|
fill: 'rgba(255,255,255,0.55)',
|
||||||
|
fontSize: 13,
|
||||||
|
fontFamily: '\'Rajdhani\', sans-serif',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
borderRadius={3}
|
||||||
|
height={220}
|
||||||
|
margin={{ top: 10, bottom: 30, left: 40, right: 140 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</ThemeProvider>
|
</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,
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* 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, { useMemo } from 'react';
|
||||||
|
import { string } from 'prop-types';
|
||||||
|
|
||||||
|
export const Avatar = ({ name, color, avatarUrl, bonusPoints = 0 }) => {
|
||||||
|
const isRed = 'red' === color;
|
||||||
|
const initials = useMemo(() => (name || '?').slice(0, 2).toUpperCase(), [name]);
|
||||||
|
|
||||||
|
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 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 className="bd-avatar-bonus">
|
||||||
|
<i className="fa fa-star" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<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,
|
||||||
|
};
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
/**
|
||||||
|
* 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 { 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
|
||||||
|
|| game?.redBonusStats?.blindHits
|
||||||
|
|| game?.blueBonusStats?.blindHits,
|
||||||
|
[
|
||||||
|
game?.blueBonusPoints,
|
||||||
|
game?.blueBonusStats?.blindHits,
|
||||||
|
game?.redBonusPoints,
|
||||||
|
game?.redBonusStats?.blindHits,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasRedNoBonuses = useMemo(
|
||||||
|
() => !game.redBonusStats?.blindHits
|
||||||
|
&& !game.redBonusStats?.chainBest
|
||||||
|
&& !game.redBonusStats?.edgeMines
|
||||||
|
&& !game.redBonusStats?.lastMineHits
|
||||||
|
&& !game.redBonusStats?.biggestReveal,
|
||||||
|
[
|
||||||
|
game.redBonusStats?.biggestReveal,
|
||||||
|
game.redBonusStats?.blindHits,
|
||||||
|
game.redBonusStats?.chainBest,
|
||||||
|
game.redBonusStats?.edgeMines,
|
||||||
|
game.redBonusStats?.lastMineHits,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasBlueNoBonuses = useMemo(
|
||||||
|
() => !game.blueBonusStats?.blindHits
|
||||||
|
&& !game.blueBonusStats?.chainBest
|
||||||
|
&& !game.blueBonusStats?.edgeMines
|
||||||
|
&& !game.blueBonusStats?.lastMineHits
|
||||||
|
&& !game.blueBonusStats?.biggestReveal,
|
||||||
|
[
|
||||||
|
game.blueBonusStats?.biggestReveal,
|
||||||
|
game.blueBonusStats?.blindHits,
|
||||||
|
game.blueBonusStats?.chainBest,
|
||||||
|
game.blueBonusStats?.edgeMines,
|
||||||
|
game.blueBonusStats?.lastMineHits,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasBonuspoints) return '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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 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 className="bd-bonus__column bd-bonus__column--blue">
|
||||||
|
<span className="bd-bonus__heading">
|
||||||
|
<i className="fa fa-star" /> Blue Bonus Statistics
|
||||||
|
</span>
|
||||||
|
<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,
|
||||||
|
};
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* 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 { node, string } from 'prop-types';
|
||||||
|
|
||||||
|
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}
|
||||||
|
>
|
||||||
|
{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';
|
||||||
@@ -11,10 +11,11 @@ import React, { useRef } from 'react';
|
|||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { GameProvider } from '@mine-contexts';
|
import { GameProvider } from '@mine-contexts';
|
||||||
import { GameBoard } from '@mine-components';
|
import { GameBoard } from '@mine-components';
|
||||||
|
import { string } from 'prop-types';
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
const MineSeeker = ({ env, gameId }) => {
|
const MineSeeker = ({ env, gameId, opponentName = '' }) => {
|
||||||
const isEnvDev = 'dev' === env;
|
const isEnvDev = 'dev' === env;
|
||||||
const gameAssoc = useRef('' !== gameId ? gameId : crypto.randomUUID()).current;
|
const gameAssoc = useRef('' !== gameId ? gameId : crypto.randomUUID()).current;
|
||||||
const gameInherited = '' !== gameId;
|
const gameInherited = '' !== gameId;
|
||||||
@@ -25,6 +26,7 @@ const MineSeeker = ({ env, gameId }) => {
|
|||||||
<GameBoard
|
<GameBoard
|
||||||
gameAssoc={gameAssoc}
|
gameAssoc={gameAssoc}
|
||||||
gameInherited={gameInherited}
|
gameInherited={gameInherited}
|
||||||
|
opponentName={opponentName}
|
||||||
isEnvDev={isEnvDev}
|
isEnvDev={isEnvDev}
|
||||||
/>
|
/>
|
||||||
</GameProvider>
|
</GameProvider>
|
||||||
@@ -33,3 +35,9 @@ const MineSeeker = ({ env, gameId }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default MineSeeker;
|
export default MineSeeker;
|
||||||
|
|
||||||
|
MineSeeker.propTypes = {
|
||||||
|
env: string.isRequired,
|
||||||
|
gameId: string,
|
||||||
|
opponentName: string,
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* 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 { func, number, string } from 'prop-types';
|
||||||
|
|
||||||
|
const BonusBox = ({ color, points, onClick, title }) => (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`bonus-box ${color}-bonus`}
|
||||||
|
onClick={onClick}
|
||||||
|
title={title || 'View bonus statistics'}
|
||||||
|
aria-label={`${color} bonus points: ${points}`}
|
||||||
|
>
|
||||||
|
<i className="fa fa-star bonus-box__icon" />
|
||||||
|
<span className="bonus-box__value">{points}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default BonusBox;
|
||||||
|
|
||||||
|
BonusBox.propTypes = {
|
||||||
|
color: string.isRequired,
|
||||||
|
points: number.isRequired,
|
||||||
|
onClick: func.isRequired,
|
||||||
|
title: string,
|
||||||
|
};
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* 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 Dialog from '@mui/material/Dialog';
|
||||||
|
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 }) => (
|
||||||
|
<StyledDialog open={open} onClose={onClose}>
|
||||||
|
<div className="bsd">
|
||||||
|
<div className="bsd-header">
|
||||||
|
<div className="bsd-header-text">
|
||||||
|
<span className="bsd-label">Scoring</span>
|
||||||
|
<h2 className="bsd-title">
|
||||||
|
<i className="fa fa-star" />
|
||||||
|
Bonus Statistics
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<button className="bsd-close" onClick={onClose} aria-label="Close">
|
||||||
|
<i className="fa fa-times" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="bsd-body">
|
||||||
|
<PlayerColumn color="red" player={red} />
|
||||||
|
<PlayerColumn color="blue" player={blue} />
|
||||||
|
</div>
|
||||||
|
<p className="bsd-note">
|
||||||
|
Bonus points are awarded alongside the main score for skillful play.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</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,
|
||||||
|
};
|
||||||
@@ -6,16 +6,28 @@
|
|||||||
* For the full copyright and license information, please view the LICENSE
|
* For the full copyright and license information, please view the LICENSE
|
||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
import React, { useEffect, 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_STORAGE_KEY = 'mineseeker_captcha_verified';
|
||||||
const CAPTCHA_TOKEN_KEY = 'mineseeker_captcha_token';
|
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 [verified, setVerified] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const capRef = useRef(null);
|
||||||
const [error, setError] = useState(false);
|
|
||||||
|
const handleToken = useCallback(token => {
|
||||||
|
const wrapper = document.getElementById('mine-wrapper');
|
||||||
|
if (wrapper) {
|
||||||
|
wrapper.dataset.captchaToken = token;
|
||||||
|
}
|
||||||
|
sessionStorage.setItem(CAPTCHA_TOKEN_KEY, token);
|
||||||
|
sessionStorage.setItem(CAPTCHA_STORAGE_KEY, Date.now().toString());
|
||||||
|
setVerified(true);
|
||||||
|
onVerified?.();
|
||||||
|
}, [onVerified]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const storedToken = sessionStorage.getItem(CAPTCHA_TOKEN_KEY);
|
const storedToken = sessionStorage.getItem(CAPTCHA_TOKEN_KEY);
|
||||||
@@ -33,137 +45,66 @@ const CaptchaOverlay = ({ siteKey, onVerified, children }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}, [onVerified]);
|
||||||
|
|
||||||
if (window.grecaptcha) {
|
useEffect(() => {
|
||||||
window.grecaptcha.ready(() => {
|
const widget = document.createElement('cap-widget');
|
||||||
window.grecaptcha
|
widget.style.display = 'none';
|
||||||
.execute(siteKey, { action: RECAPTCHA_ACTION })
|
capRef.current = widget;
|
||||||
.then(token => {
|
document.body.appendChild(widget);
|
||||||
handleToken(token);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setError(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [siteKey, onVerified]);
|
|
||||||
|
|
||||||
const handleToken = token => {
|
const cap = new window.Cap({ apiEndpoint }, widget);
|
||||||
const wrapper = document.getElementById('mine-wrapper');
|
let cancelled = false;
|
||||||
if (wrapper) {
|
|
||||||
wrapper.dataset.captchaToken = token;
|
const run = async () => {
|
||||||
|
try {
|
||||||
|
const result = await cap.solve();
|
||||||
|
if (!cancelled && result?.token) {
|
||||||
|
handleToken(result.token);
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
if (!cancelled) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!cancelled) {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
}, 1200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sessionStorage.setItem(CAPTCHA_TOKEN_KEY, token);
|
|
||||||
sessionStorage.setItem(CAPTCHA_STORAGE_KEY, Date.now().toString());
|
|
||||||
setVerified(true);
|
|
||||||
onVerified?.();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClick = () => {
|
run();
|
||||||
setLoading(true);
|
|
||||||
setError(false);
|
|
||||||
|
|
||||||
window.grecaptcha.ready(() => {
|
return () => {
|
||||||
window.grecaptcha
|
cancelled = true;
|
||||||
.execute(siteKey, { action: RECAPTCHA_ACTION })
|
widget.remove();
|
||||||
.then(token => {
|
capRef.current = null;
|
||||||
handleToken(token);
|
|
||||||
setLoading(false);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setLoading(false);
|
|
||||||
setError(true);
|
|
||||||
setTimeout(() => setError(false), 2000);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
}, [apiEndpoint, handleToken]);
|
||||||
|
|
||||||
if (verified) {
|
if (verified) {
|
||||||
return <>{children}</>;
|
return <Fragment>{children}</Fragment>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const overlayStyles = {
|
|
||||||
position: 'fixed',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
background: 'rgba(7, 9, 13, 0.95)',
|
|
||||||
backdropFilter: 'blur(8px)',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
zIndex: 1000,
|
|
||||||
};
|
|
||||||
|
|
||||||
const contentStyles = {
|
|
||||||
textAlign: 'center',
|
|
||||||
color: '#fff',
|
|
||||||
maxWidth: '400px',
|
|
||||||
padding: '40px',
|
|
||||||
};
|
|
||||||
|
|
||||||
const iconStyles = {
|
|
||||||
fontSize: '64px',
|
|
||||||
color: '#236f87',
|
|
||||||
marginBottom: '24px',
|
|
||||||
};
|
|
||||||
|
|
||||||
const h1Styles = {
|
|
||||||
font: '800 32px Rajdhani, sans-serif',
|
|
||||||
margin: '0 0 16px',
|
|
||||||
letterSpacing: '1px',
|
|
||||||
};
|
|
||||||
|
|
||||||
const pStyles = {
|
|
||||||
color: 'rgba(149, 207, 245, 0.7)',
|
|
||||||
font: '400 16px Rajdhani, sans-serif',
|
|
||||||
margin: '0 0 32px',
|
|
||||||
letterSpacing: '0.5px',
|
|
||||||
};
|
|
||||||
|
|
||||||
const buttonStyles = {
|
|
||||||
background: error
|
|
||||||
? 'linear-gradient(#8a2323 0%, #681a1a 100%)'
|
|
||||||
: loading
|
|
||||||
? 'linear-gradient(#236f87 0%, #1a5068 100%)'
|
|
||||||
: 'linear-gradient(#236f87 0%, #1a5068 100%)',
|
|
||||||
border: `2px solid ${error ? '#9a2e2e' : loading ? '#2e7a9a' : '#2e7a9a'}`,
|
|
||||||
borderRadius: '8px',
|
|
||||||
color: '#e0f4ff',
|
|
||||||
cursor: loading ? 'wait' : 'pointer',
|
|
||||||
font: '800 18px Rajdhani, sans-serif',
|
|
||||||
letterSpacing: '2px',
|
|
||||||
padding: '16px 40px',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
transition: 'all 0.3s ease',
|
|
||||||
display: 'inline-flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '12px',
|
|
||||||
opacity: loading ? 0.7 : 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={overlayStyles}>
|
<div className="captcha-overlay">
|
||||||
<div style={contentStyles}>
|
<div className="captcha-content">
|
||||||
<div style={iconStyles}>
|
<div className="captcha-icon">
|
||||||
<i className="fa fa-shield-halved" />
|
<i className="fa fa-shield-halved" />
|
||||||
</div>
|
</div>
|
||||||
<h1 style={h1Styles}>Ready to Play?</h1>
|
<h1 className="captcha-title">Ready to Play?</h1>
|
||||||
<p style={pStyles}>
|
<p className="captcha-description">
|
||||||
Click below to verify you're human and start playing.
|
Verifying your session...
|
||||||
</p>
|
</p>
|
||||||
<button
|
|
||||||
style={buttonStyles}
|
|
||||||
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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CaptchaOverlay;
|
export default CaptchaOverlay;
|
||||||
|
|
||||||
|
CaptchaOverlay.propTypes = {
|
||||||
|
apiEndpoint: string.isRequired,
|
||||||
|
onVerified: func,
|
||||||
|
children: node,
|
||||||
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
import { Fragment, useEffect, useState } from 'react';
|
import { Fragment, useEffect, useState } from 'react';
|
||||||
|
import { func, number } from 'prop-types';
|
||||||
|
|
||||||
const ChallengeCountdown = ({ onAccept, onDecline, seconds = 30 }) => {
|
const ChallengeCountdown = ({ onAccept, onDecline, seconds = 30 }) => {
|
||||||
const [countdown, setCountdown] = useState(seconds);
|
const [countdown, setCountdown] = useState(seconds);
|
||||||
@@ -39,3 +40,9 @@ const ChallengeCountdown = ({ onAccept, onDecline, seconds = 30 }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default ChallengeCountdown;
|
export default ChallengeCountdown;
|
||||||
|
|
||||||
|
ChallengeCountdown.propTypes = {
|
||||||
|
onAccept: func.isRequired,
|
||||||
|
onDecline: func.isRequired,
|
||||||
|
seconds: number,
|
||||||
|
};
|
||||||
|
|||||||
@@ -7,14 +7,19 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useGame } from '@mine-contexts';
|
import { useGame } from '@mine-contexts';
|
||||||
import { useServerCommunication } from '@mine-hooks';
|
import { useServerCommunication } from '@mine-hooks';
|
||||||
|
import CaptchaOverlay from './CaptchaOverlay';
|
||||||
import GridControl from './grid/GridControl';
|
import GridControl from './grid/GridControl';
|
||||||
|
import { bool, string } from 'prop-types';
|
||||||
|
|
||||||
export const GameBoard = ({ gameAssoc, gameInherited, isEnvDev }) => {
|
export const GameBoard = ({ gameAssoc, gameInherited, opponentName = '', isEnvDev }) => {
|
||||||
const { gridReady } = useGame();
|
const { gridReady } = useGame();
|
||||||
const { onClick, resign } = useServerCommunication(gameAssoc, gameInherited, isEnvDev);
|
const { onClick, resign } = useServerCommunication(gameAssoc, gameInherited, opponentName, isEnvDev);
|
||||||
|
const [captchaVerified, setCaptchaVerified] = useState(false);
|
||||||
|
|
||||||
|
const apiEndpoint = document.getElementById('mine-wrapper')?.dataset.capApiEndpoint;
|
||||||
|
|
||||||
if (!gridReady) {
|
if (!gridReady) {
|
||||||
return (
|
return (
|
||||||
@@ -24,6 +29,12 @@ export const GameBoard = ({ gameAssoc, gameInherited, isEnvDev }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!captchaVerified && apiEndpoint) {
|
||||||
|
return (
|
||||||
|
<CaptchaOverlay apiEndpoint={apiEndpoint} onVerified={() => setCaptchaVerified(true)} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GridControl
|
<GridControl
|
||||||
gameAssoc={gameAssoc}
|
gameAssoc={gameAssoc}
|
||||||
@@ -32,3 +43,10 @@ export const GameBoard = ({ gameAssoc, gameInherited, isEnvDev }) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
GameBoard.propTypes = {
|
||||||
|
gameAssoc: string.isRequired,
|
||||||
|
gameInherited: bool.isRequired,
|
||||||
|
opponentName: string,
|
||||||
|
isEnvDev: bool,
|
||||||
|
};
|
||||||
|
|||||||
@@ -7,37 +7,26 @@
|
|||||||
* file that was distributed with this source code.
|
* 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 { useGame } from '@mine-contexts';
|
||||||
|
|
||||||
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 GameTimer = () => {
|
||||||
const { overlay, connectionLost, endRef, activePlayer, red, blue } = useGame();
|
const { overlay, connectionLost, endRef, activePlayer, red, blue } = useGame();
|
||||||
const [redTime, setRedTime] = useState(0);
|
const [redTime, setRedTime] = useState(0);
|
||||||
const [blueTime, setBlueTime] = useState(0);
|
const [blueTime, setBlueTime] = useState(0);
|
||||||
const [isRunning, setIsRunning] = useState(false);
|
const [isRunning, setIsRunning] = useState(false);
|
||||||
|
const [bonusDialogOpen, setBonusDialogOpen] = useState(false);
|
||||||
const timerIntervalRef = useRef(null);
|
const timerIntervalRef = useRef(null);
|
||||||
const gameStartedRef = useRef(false);
|
const gameStartedRef = useRef(false);
|
||||||
|
|
||||||
// Use timestamps instead of counters for more reliable background tracking
|
|
||||||
const redStartTimeRef = useRef(null);
|
const redStartTimeRef = useRef(null);
|
||||||
const blueStartTimeRef = useRef(null);
|
const blueStartTimeRef = useRef(null);
|
||||||
const lastActivePlayerRef = useRef(null);
|
const lastActivePlayerRef = useRef(null);
|
||||||
const pausedRedTimeRef = useRef(0);
|
const pausedRedTimeRef = useRef(0);
|
||||||
const pausedBlueTimeRef = useRef(0);
|
const pausedBlueTimeRef = useRef(0);
|
||||||
|
|
||||||
// Start timer when overlay is hidden (both players connected and game started)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!overlay && !gameStartedRef.current) {
|
if (!overlay && !gameStartedRef.current) {
|
||||||
gameStartedRef.current = true;
|
gameStartedRef.current = true;
|
||||||
@@ -50,28 +39,20 @@ const GameTimer = () => {
|
|||||||
pausedBlueTimeRef.current = 0;
|
pausedBlueTimeRef.current = 0;
|
||||||
lastActivePlayerRef.current = activePlayer;
|
lastActivePlayerRef.current = activePlayer;
|
||||||
}
|
}
|
||||||
}, [overlay]);
|
}, [activePlayer, overlay]);
|
||||||
|
|
||||||
// Stop timer on game end (resign/win)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (endRef.current) {
|
if (endRef.current) setIsRunning(false);
|
||||||
setIsRunning(false);
|
}, [endRef]);
|
||||||
}
|
|
||||||
}, [endRef.current]);
|
|
||||||
|
|
||||||
// Stop timer on connection loss
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (connectionLost) {
|
if (connectionLost) setIsRunning(false);
|
||||||
setIsRunning(false);
|
|
||||||
}
|
|
||||||
}, [connectionLost]);
|
}, [connectionLost]);
|
||||||
|
|
||||||
// Handle player switch - pause one timer, resume the other
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isRunning) return;
|
if (!isRunning) return;
|
||||||
|
|
||||||
if (lastActivePlayerRef.current !== activePlayer) {
|
if (lastActivePlayerRef.current !== activePlayer) {
|
||||||
// Player switched, save current accumulated time for whoever was active
|
|
||||||
const startRef = lastActivePlayerRef.current ? blueStartTimeRef.current : redStartTimeRef.current;
|
const startRef = lastActivePlayerRef.current ? blueStartTimeRef.current : redStartTimeRef.current;
|
||||||
if (startRef) {
|
if (startRef) {
|
||||||
const elapsed = Math.floor((Date.now() - startRef) / 1000);
|
const elapsed = Math.floor((Date.now() - startRef) / 1000);
|
||||||
@@ -82,7 +63,6 @@ const GameTimer = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the new active player's timer
|
|
||||||
if (activePlayer) {
|
if (activePlayer) {
|
||||||
blueStartTimeRef.current = Date.now();
|
blueStartTimeRef.current = Date.now();
|
||||||
} else {
|
} else {
|
||||||
@@ -93,85 +73,61 @@ const GameTimer = () => {
|
|||||||
}
|
}
|
||||||
}, [activePlayer, isRunning]);
|
}, [activePlayer, isRunning]);
|
||||||
|
|
||||||
// Main timer effect - update display every 100ms
|
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(() => {
|
useEffect(() => {
|
||||||
if (!isRunning) {
|
if (!isRunning) {
|
||||||
if (timerIntervalRef.current) {
|
if (timerIntervalRef.current) clearInterval(timerIntervalRef.current);
|
||||||
clearInterval(timerIntervalRef.current);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
timerIntervalRef.current = setInterval(() => {
|
timerIntervalRef.current = setInterval(syncTimes, 100);
|
||||||
let currentRedTime = pausedRedTimeRef.current;
|
|
||||||
let currentBlueTime = pausedBlueTimeRef.current;
|
|
||||||
|
|
||||||
// Add elapsed time for the active player
|
|
||||||
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);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (timerIntervalRef.current) {
|
if (timerIntervalRef.current) clearInterval(timerIntervalRef.current);
|
||||||
clearInterval(timerIntervalRef.current);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}, [isRunning, activePlayer]);
|
}, [isRunning, activePlayer, syncTimes]);
|
||||||
|
|
||||||
// Handle focus/blur to synchronize timer when tab regains focus
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleFocus = () => {
|
const handleFocus = () => {
|
||||||
// Force update when tab regains focus to sync any background drift
|
if (isRunning) syncTimes();
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('focus', handleFocus);
|
window.addEventListener('focus', handleFocus);
|
||||||
return () => window.removeEventListener('focus', handleFocus);
|
return () => window.removeEventListener('focus', handleFocus);
|
||||||
}, [isRunning, activePlayer]);
|
}, [isRunning, activePlayer, syncTimes]);
|
||||||
|
|
||||||
// Cleanup on unmount
|
|
||||||
useEffect(() => () => {
|
useEffect(() => () => {
|
||||||
if (timerIntervalRef.current) {
|
if (timerIntervalRef.current) clearInterval(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')}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="game-timer-container">
|
<div className="game-timer-container">
|
||||||
|
<BonusBox color="red" points={red.bonusPoints ?? 0} onClick={() => setBonusDialogOpen(true)} />
|
||||||
<div className={`game-timer red-timer ${!activePlayer ? 'active' : ''}`}>
|
<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`} />
|
<i className={`fa ${!activePlayer ? 'fa-hourglass-half' : 'fa-hourglass-start'} timer-icon`} />
|
||||||
<span className="timer-display">{formatTime(redTime)}</span>
|
<span className="timer-display">{formatTime(redTime)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={`game-timer blue-timer ${activePlayer ? 'active' : ''}`}>
|
<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`} />
|
<i className={`fa ${activePlayer ? 'fa-hourglass-half' : 'fa-hourglass-start'} timer-icon`} />
|
||||||
<span className="timer-display">{formatTime(blueTime)}</span>
|
<span className="timer-display">{formatTime(blueTime)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<BonusBox color="blue" points={blue.bonusPoints ?? 0} onClick={() => setBonusDialogOpen(true)} />
|
||||||
|
<BonusStatsDialog open={bonusDialogOpen} onClose={() => setBonusDialogOpen(false)} red={red} blue={blue} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,38 +8,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import { formatSince } from '@global-utils/format';
|
||||||
import Dialog from '@mui/material/Dialog';
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import { styled } from '@mui/material/styles';
|
||||||
|
import { useLobbyDataProvider } from '@mine-hooks';
|
||||||
|
import { bool, func, string } from 'prop-types';
|
||||||
|
|
||||||
const DIALOG_SX = {
|
const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc, isEnvDev = false }) => {
|
||||||
'& .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`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc }) => {
|
|
||||||
const [players, setPlayers] = useState([]);
|
const [players, setPlayers] = useState([]);
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -49,6 +24,7 @@ const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc }) => {
|
|||||||
const [declinedMsg, setDeclinedMsg] = useState('');
|
const [declinedMsg, setDeclinedMsg] = useState('');
|
||||||
const [waitingCountdown, setWaitingCountdown] = useState(0);
|
const [waitingCountdown, setWaitingCountdown] = useState(0);
|
||||||
const declinedTimerRef = useRef(null);
|
const declinedTimerRef = useRef(null);
|
||||||
|
const { waitingPlayersQuery, challengeMutation } = useLobbyDataProvider();
|
||||||
|
|
||||||
const addPlayer = useCallback(entry => {
|
const addPlayer = useCallback(entry => {
|
||||||
setPlayers(prev =>
|
setPlayers(prev =>
|
||||||
@@ -66,20 +42,21 @@ const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc }) => {
|
|||||||
if (!open) return;
|
if (!open) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setSnapshotLoaded(false);
|
setSnapshotLoaded(false);
|
||||||
fetch('/api/game/waiting')
|
|
||||||
.then(r => r.json())
|
waitingPlayersQuery.refetch().then(result => {
|
||||||
.then(data => {
|
if (result.data) {
|
||||||
// Filter out current user's game from the snapshot
|
// Filter out current user's game from the snapshot
|
||||||
const filtered = data.filter(p => p.gameAssoc !== currentGameAssoc);
|
const filtered = result.data.filter(p => p.gameAssoc !== currentGameAssoc);
|
||||||
setPlayers(filtered);
|
setPlayers(filtered);
|
||||||
|
}
|
||||||
setSnapshotLoaded(true);
|
setSnapshotLoaded(true);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
})
|
}).catch(() => {
|
||||||
.catch(() => {
|
|
||||||
setPlayers([]);
|
setPlayers([]);
|
||||||
setSnapshotLoaded(true);
|
setSnapshotLoaded(true);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [open, refreshKey, currentGameAssoc]);
|
}, [open, refreshKey, currentGameAssoc]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -107,6 +84,13 @@ const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc }) => {
|
|||||||
return () => es.close();
|
return () => es.close();
|
||||||
}, [open, snapshotLoaded, addPlayer, removePlayer, currentGameAssoc]);
|
}, [open, snapshotLoaded, addPlayer, removePlayer, currentGameAssoc]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (challengeMutation.isError) {
|
||||||
|
setChallengingGameAssoc(null);
|
||||||
|
setWaitingCountdown(0);
|
||||||
|
}
|
||||||
|
}, [challengeMutation.isError]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = () => {
|
const handler = () => {
|
||||||
setChallengingGameAssoc(null);
|
setChallengingGameAssoc(null);
|
||||||
@@ -138,14 +122,10 @@ const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc }) => {
|
|||||||
setChallengingGameAssoc(player.gameAssoc);
|
setChallengingGameAssoc(player.gameAssoc);
|
||||||
setDeclinedMsg('');
|
setDeclinedMsg('');
|
||||||
setWaitingCountdown(30);
|
setWaitingCountdown(30);
|
||||||
fetch('/api/game/challenge/' + player.gameAssoc, {
|
|
||||||
method: 'POST',
|
challengeMutation.mutate(
|
||||||
headers: { 'Content-Type': 'application/json' },
|
{ targetGameAssoc: player.gameAssoc, challengerGameAssoc: currentGameAssoc }
|
||||||
body: JSON.stringify({ challengerGameAssoc: currentGameAssoc }),
|
);
|
||||||
}).catch(() => {
|
|
||||||
setChallengingGameAssoc(null);
|
|
||||||
setWaitingCountdown(0);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const visible = players
|
const visible = players
|
||||||
@@ -156,19 +136,18 @@ const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc }) => {
|
|||||||
const hasMore = 5 < visible.length;
|
const hasMore = 5 < visible.length;
|
||||||
|
|
||||||
// Debug: log if currentGameAssoc is undefined or if current user appears
|
// Debug: log if currentGameAssoc is undefined or if current user appears
|
||||||
if ('development' === process.env.NODE_ENV && 0 < players.length) {
|
if (isEnvDev && 0 < players.length) {
|
||||||
const userInList = players.find(p => p.gameAssoc === currentGameAssoc);
|
const userInList = players.find(p => p.gameAssoc === currentGameAssoc);
|
||||||
|
|
||||||
if (userInList) {
|
if (userInList) {
|
||||||
console.warn('[OnlinePlayersDialog] Current user appears in players list:', { currentGameAssoc, userInList });
|
console.warn('[OnlinePlayersDialog] Current user appears in players list:', { currentGameAssoc, userInList });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<StyledDialog
|
||||||
open={open}
|
open={open}
|
||||||
onClose={0 < waitingCountdown ? undefined : onClose}
|
onClose={0 < waitingCountdown ? undefined : onClose}
|
||||||
disableEscapeKeyDown={0 < waitingCountdown}
|
|
||||||
sx={DIALOG_SX}
|
|
||||||
>
|
>
|
||||||
<div className="opd">
|
<div className="opd">
|
||||||
<div className="opd-header">
|
<div className="opd-header">
|
||||||
@@ -256,7 +235,7 @@ const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc }) => {
|
|||||||
<div className="opd-info">
|
<div className="opd-info">
|
||||||
<span className="opd-name">{player.name}</span>
|
<span className="opd-name">{player.name}</span>
|
||||||
<span className="opd-since">
|
<span className="opd-since">
|
||||||
<i className="fa fa-clock-o" />
|
<i className="fa fa-clock" />
|
||||||
{' '}Waiting {formatSince(player.since)}
|
{' '}Waiting {formatSince(player.since)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -279,8 +258,37 @@ const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc }) => {
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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;
|
export default OnlinePlayersDialog;
|
||||||
|
|
||||||
|
OnlinePlayersDialog.propTypes = {
|
||||||
|
open: bool.isRequired,
|
||||||
|
onClose: func.isRequired,
|
||||||
|
currentGameAssoc: string,
|
||||||
|
isEnvDev: bool,
|
||||||
|
};
|
||||||
|
|||||||
@@ -8,23 +8,29 @@
|
|||||||
*/
|
*/
|
||||||
import { Fragment, useState } from 'react';
|
import { Fragment, useState } from 'react';
|
||||||
import { OnlinePlayersDialog } from '@mine-components';
|
import { OnlinePlayersDialog } from '@mine-components';
|
||||||
|
import { bool, string } from 'prop-types';
|
||||||
|
|
||||||
const WaitingOverlayContent = ({ shareUrl, currentGameAssoc }) => {
|
const WaitingOverlayContent = ({ shareUrl, currentGameAssoc, opponentName = '', inviteOnly = false }) => {
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
|
const inviteHeader = inviteOnly && opponentName
|
||||||
|
? `Invite ${opponentName}`
|
||||||
|
: 'Invite a Friend';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="waiting-options">
|
<div className={`waiting-options${inviteOnly ? ' waiting-options--invite-only' : ''}`}>
|
||||||
<div className="waiting-option">
|
<div className="waiting-option">
|
||||||
<div className="waiting-option-header">
|
<div className="waiting-option-header">
|
||||||
<i className="fa fa-link" />
|
<i className="fa fa-link" />
|
||||||
<span>Invite a Friend</span>
|
<span>{inviteHeader}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="waiting-option-desc">Share this link with your opponent</p>
|
<p className="waiting-option-desc">Share this link with your opponent</p>
|
||||||
<ShareLinkBox
|
<ShareLinkBox
|
||||||
url={shareUrl}
|
url={shareUrl}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{!inviteOnly && (
|
||||||
|
<Fragment>
|
||||||
<div className="waiting-divider">
|
<div className="waiting-divider">
|
||||||
<span>OR</span>
|
<span>OR</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,13 +48,17 @@ const WaitingOverlayContent = ({ shareUrl, currentGameAssoc }) => {
|
|||||||
Browse Players
|
Browse Players
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{!inviteOnly && (
|
||||||
<OnlinePlayersDialog
|
<OnlinePlayersDialog
|
||||||
open={dialogOpen}
|
open={dialogOpen}
|
||||||
onClose={() => setDialogOpen(false)}
|
onClose={() => setDialogOpen(false)}
|
||||||
currentGameAssoc={currentGameAssoc}
|
currentGameAssoc={currentGameAssoc}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -57,10 +67,12 @@ const ShareLinkBox = ({ url }) => {
|
|||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
const handleCopy = () => {
|
const handleCopy = () => {
|
||||||
navigator.clipboard.writeText(url).then(() => {
|
navigator.clipboard.writeText(url)
|
||||||
|
.then(() => {
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
setTimeout(() => setCopied(false), 2500);
|
setTimeout(() => setCopied(false), 2500);
|
||||||
}).catch(() => {});
|
})
|
||||||
|
.catch(() => null);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -83,3 +95,10 @@ const ShareLinkBox = ({ url }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default WaitingOverlayContent;
|
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 UserControl from '../user/UserControl';
|
||||||
import GameTimer from '../GameTimer';
|
import GameTimer from '../GameTimer';
|
||||||
import { BOMB_SYMBOLS, bombRadius } from '@mine-utils';
|
import { BOMB_SYMBOLS, bombRadius } from '@mine-utils';
|
||||||
|
import { func, string } from 'prop-types';
|
||||||
|
|
||||||
const GridControl = ({ gameAssoc, onClick, resign }) => {
|
const GridControl = ({ gameAssoc, onClick, resign }) => {
|
||||||
const {
|
const {
|
||||||
@@ -22,11 +23,14 @@ const GridControl = ({ gameAssoc, onClick, resign }) => {
|
|||||||
} = useGame();
|
} = useGame();
|
||||||
|
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const shareUrl = gameAssoc ? `${window.location.origin}/battle/${gameAssoc}` : null;
|
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 = () => {
|
const handleShare = () => {
|
||||||
if (!shareUrl) return;
|
const url = endRef.current ? endShareUrl : shareUrl;
|
||||||
navigator.clipboard.writeText(shareUrl).then(() => {
|
if (!url) return;
|
||||||
|
navigator.clipboard.writeText(url).then(() => {
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
setTimeout(() => setCopied(false), 2200);
|
setTimeout(() => setCopied(false), 2200);
|
||||||
});
|
});
|
||||||
@@ -58,12 +62,14 @@ const GridControl = ({ gameAssoc, onClick, resign }) => {
|
|||||||
<div className={`game-overlay${overlay ? '' : ' hide'}`}>
|
<div className={`game-overlay${overlay ? '' : ' hide'}`}>
|
||||||
<div className="game-overlay-window">
|
<div className="game-overlay-window">
|
||||||
<h1>{overlayTitle}</h1>
|
<h1>{overlayTitle}</h1>
|
||||||
{'string' === typeof overlaySubTitle ? (
|
{'string' === typeof overlaySubTitle && (
|
||||||
<h2>{overlaySubTitle}</h2>
|
<h2>{overlaySubTitle}</h2>
|
||||||
) : (
|
)}
|
||||||
overlaySubTitle
|
{'string' !== typeof overlaySubTitle && (
|
||||||
|
<Fragment>{overlaySubTitle}</Fragment>
|
||||||
)}
|
)}
|
||||||
{gameAssoc && endRef.current && (
|
{gameAssoc && endRef.current && (
|
||||||
|
<div className="game-overlay-actions">
|
||||||
<button
|
<button
|
||||||
className={`game-overlay-share${copied ? ' copied' : ''}`}
|
className={`game-overlay-share${copied ? ' copied' : ''}`}
|
||||||
onClick={handleShare}
|
onClick={handleShare}
|
||||||
@@ -73,6 +79,16 @@ const GridControl = ({ gameAssoc, onClick, resign }) => {
|
|||||||
<i className={`fa ${copied ? 'fa-check' : 'fa-share-alt'}`} />
|
<i className={`fa ${copied ? 'fa-check' : 'fa-share-alt'}`} />
|
||||||
{copied ? 'Copied!' : 'Share Battle'}
|
{copied ? 'Copied!' : 'Share Battle'}
|
||||||
</button>
|
</button>
|
||||||
|
<a
|
||||||
|
className="game-overlay-profile"
|
||||||
|
href={isAuthenticated ? '/profile' : '/'}
|
||||||
|
title={isAuthenticated ? 'Go to your profile' : 'Go to homepage'}
|
||||||
|
aria-label={isAuthenticated ? 'Go to your profile' : 'Go to homepage'}
|
||||||
|
>
|
||||||
|
<i className={`fa ${isAuthenticated ? 'fa-user' : 'fa-house'}`} />
|
||||||
|
{isAuthenticated ? 'My Profile' : 'Homepage'}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,3 +115,9 @@ const GridControl = ({ gameAssoc, onClick, resign }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default GridControl;
|
export default GridControl;
|
||||||
|
|
||||||
|
GridControl.propTypes = {
|
||||||
|
gameAssoc: string,
|
||||||
|
onClick: func.isRequired,
|
||||||
|
resign: func.isRequired,
|
||||||
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
import React, { memo, useMemo } from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
import { IMAGES } from '@mine-utils';
|
import { IMAGES } from '@mine-utils';
|
||||||
|
import { func, shape, bool, number, string } from 'prop-types';
|
||||||
|
|
||||||
const bombSrc = area => {
|
const bombSrc = area => {
|
||||||
if (null === area) return null;
|
if (null === area) return null;
|
||||||
@@ -53,7 +54,10 @@ const GridField = memo(function GridField({ cell, onClick, onMouseEnter }) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className={fieldClass}>
|
<div className={fieldClass}>
|
||||||
<div className="field-corner">
|
<div
|
||||||
|
style={{ background: "url('/images/bg-corner-outbg.png') no-repeat top left / 100% 100%" }}
|
||||||
|
className="field-corner"
|
||||||
|
>
|
||||||
{isNaN(currentImage) && (
|
{isNaN(currentImage) && (
|
||||||
<div className="flag-mine">
|
<div className="flag-mine">
|
||||||
<img src={currentImage} alt="" />
|
<img src={currentImage} alt="" />
|
||||||
@@ -72,3 +76,16 @@ const GridField = memo(function GridField({ cell, onClick, onMouseEnter }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default GridField;
|
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 GridField } from './grid/GridField';
|
||||||
export { default as User } from './user/User';
|
export { default as User } from './user/User';
|
||||||
export { default as UserControl } from './user/UserControl';
|
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 React, { memo } from 'react';
|
||||||
import { IMAGES } from '@mine-utils';
|
import { IMAGES } from '@mine-utils';
|
||||||
|
import { bool, func, number, string } from 'prop-types';
|
||||||
|
|
||||||
const User = memo(function User(
|
const User = memo(function User(
|
||||||
{
|
{
|
||||||
@@ -52,3 +53,15 @@ const User = memo(function User(
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default 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,
|
||||||
|
};
|
||||||
|
|||||||
@@ -7,15 +7,19 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { Fragment, useState } from 'react';
|
||||||
import { useGame } from '@mine-contexts';
|
import { useGame } from '@mine-contexts';
|
||||||
import User from './User';
|
import User from './User';
|
||||||
|
import BonusStatsDialog from '../BonusStatsDialog';
|
||||||
|
import { func } from 'prop-types';
|
||||||
|
|
||||||
const UserControl = ({ resign }) => {
|
const UserControl = ({ resign }) => {
|
||||||
const { webPlayer, activePlayer, mines, foundMines, red, blue, onBombToggle } = useGame();
|
const { webPlayer, activePlayer, foundMines, red, blue, onBombToggle } = useGame();
|
||||||
|
const [bonusDialogOpen, setBonusDialogOpen] = useState(false);
|
||||||
const activeColor = activePlayer ? 'blue' : 'red';
|
const activeColor = activePlayer ? 'blue' : 'red';
|
||||||
const resignClass = 'resign' + (activeColor !== webPlayer ? ' disabled' : '');
|
const resignClass = 'resign' + (activeColor !== webPlayer ? ' disabled' : '');
|
||||||
const minesClass = 'active-mines' + (foundMines ? ' found-mine' : '');
|
const minesClass = 'active-mines' + (foundMines ? ' found-mine' : '');
|
||||||
|
const remainingMines = 51 - red.mines - blue.mines;
|
||||||
|
|
||||||
const handleBombClick = (color, player) => {
|
const handleBombClick = (color, player) => {
|
||||||
const p = 'red' === color ? red : blue;
|
const p = 'red' === color ? red : blue;
|
||||||
@@ -24,16 +28,22 @@ const UserControl = ({ resign }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleBonusClick = () => {
|
||||||
|
setBonusDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Fragment>
|
||||||
<div className="users">
|
<div className="users">
|
||||||
<User
|
<User
|
||||||
color="blue" webPlayer={webPlayer} {...blue}
|
color="blue" webPlayer={webPlayer} {...blue}
|
||||||
onClickBombSelector={() => handleBombClick('blue', 1)}
|
onClickBombSelector={() => handleBombClick('blue', 1)}
|
||||||
|
onBonusClick={handleBonusClick}
|
||||||
/>
|
/>
|
||||||
<div className="active-mines-container">
|
<div className="active-mines-container">
|
||||||
<i className="fa fa-star" />
|
<i className="fa fa-star" />
|
||||||
<div className={minesClass}>
|
<div className={minesClass}>
|
||||||
<div className="active-mines-nbr">{mines}</div>
|
<div className="active-mines-nbr">{remainingMines}</div>
|
||||||
<div className="active-mines-shine" />
|
<div className="active-mines-shine" />
|
||||||
</div>
|
</div>
|
||||||
<i className="fa fa-star" />
|
<i className="fa fa-star" />
|
||||||
@@ -42,13 +52,25 @@ const UserControl = ({ resign }) => {
|
|||||||
<User
|
<User
|
||||||
color="red" webPlayer={webPlayer} {...red}
|
color="red" webPlayer={webPlayer} {...red}
|
||||||
onClickBombSelector={() => handleBombClick('red', 0)}
|
onClickBombSelector={() => handleBombClick('red', 0)}
|
||||||
|
onBonusClick={handleBonusClick}
|
||||||
/>
|
/>
|
||||||
<button className={resignClass} onClick={resign}>
|
<button className={resignClass} onClick={resign}>
|
||||||
<div className="resign-shine" />
|
<div className="resign-shine" />
|
||||||
Resign
|
Resign
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<BonusStatsDialog
|
||||||
|
open={bonusDialogOpen}
|
||||||
|
onClose={() => setBonusDialogOpen(false)}
|
||||||
|
red={red}
|
||||||
|
blue={blue}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UserControl;
|
export default UserControl;
|
||||||
|
|
||||||
|
UserControl.propTypes = {
|
||||||
|
resign: func.isRequired,
|
||||||
|
};
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export const GameProvider = ({ children }) => {
|
|||||||
mine: new Howl({ src: ['/sound/mine.mp3'] }),
|
mine: new Howl({ src: ['/sound/mine.mp3'] }),
|
||||||
warning: new Howl({ src: ['/sound/warning.mp3'] }),
|
warning: new Howl({ src: ['/sound/warning.mp3'] }),
|
||||||
won: new Howl({ src: ['/sound/won.mp3'] }),
|
won: new Howl({ src: ['/sound/won.mp3'] }),
|
||||||
|
starting: new Howl({ src: ['/sound/starting.mp3'] }),
|
||||||
});
|
});
|
||||||
|
|
||||||
/** Sync helpers (keep ref + state in lockstep) */
|
/** Sync helpers (keep ref + state in lockstep) */
|
||||||
@@ -132,7 +133,18 @@ export const GameProvider = ({ children }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const applyStep = stepData => {
|
const applyStep = stepData => {
|
||||||
const { player, bomb: isBomb, minesFound = 0, revealedCells = [], redPoints: rp, bluePoints: bp } = stepData;
|
const {
|
||||||
|
player,
|
||||||
|
bomb: isBomb,
|
||||||
|
minesFound = 0,
|
||||||
|
revealedCells = [],
|
||||||
|
redPoints: rp,
|
||||||
|
bluePoints: bp,
|
||||||
|
redBonusPoints = 0,
|
||||||
|
blueBonusPoints = 0,
|
||||||
|
redBonusStats = {},
|
||||||
|
blueBonusStats = {},
|
||||||
|
} = stepData;
|
||||||
|
|
||||||
if (isBomb) {
|
if (isBomb) {
|
||||||
sounds.current.bomb.play();
|
sounds.current.bomb.play();
|
||||||
@@ -176,6 +188,18 @@ export const GameProvider = ({ children }) => {
|
|||||||
syncBlue(p => ({ ...p, mines: 'blue' === player ? bp : p.mines }));
|
syncBlue(p => ({ ...p, mines: 'blue' === player ? bp : p.mines }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Update bonus points and stats */
|
||||||
|
syncRed(p => ({
|
||||||
|
...p,
|
||||||
|
bonusPoints: 'red' === player ? redBonusPoints : p.bonusPoints,
|
||||||
|
bonusStats: 'red' === player ? redBonusStats : p.bonusStats,
|
||||||
|
}));
|
||||||
|
syncBlue(p => ({
|
||||||
|
...p,
|
||||||
|
bonusPoints: 'blue' === player ? blueBonusPoints : p.bonusPoints,
|
||||||
|
bonusStats: 'blue' === player ? blueBonusStats : p.bonusStats,
|
||||||
|
}));
|
||||||
|
|
||||||
syncRed(p => ({ ...p, enabledBomb: rp <= bp }));
|
syncRed(p => ({ ...p, enabledBomb: rp <= bp }));
|
||||||
syncBlue(p => ({ ...p, enabledBomb: bp <= rp }));
|
syncBlue(p => ({ ...p, enabledBomb: bp <= rp }));
|
||||||
|
|
||||||
@@ -195,7 +219,10 @@ export const GameProvider = ({ children }) => {
|
|||||||
if (redWins || blueWins || resign) {
|
if (redWins || blueWins || resign) {
|
||||||
sounds.current.won.play();
|
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);
|
showLeftMines(leftMines);
|
||||||
syncActivePlayer(false);
|
syncActivePlayer(false);
|
||||||
@@ -228,21 +255,23 @@ export const GameProvider = ({ children }) => {
|
|||||||
return (
|
return (
|
||||||
<GameContext.Provider
|
<GameContext.Provider
|
||||||
value={{
|
value={{
|
||||||
// State (for rendering)
|
/** State (for rendering) */
|
||||||
webPlayer, activePlayer, overlay, overlayTitle, overlaySubTitle,
|
webPlayer, activePlayer, overlay, overlayTitle, overlaySubTitle,
|
||||||
mines, bombSelected, foundMines, red, blue, cells, gridReady, connectionLost, gameUuid,
|
mines, bombSelected, foundMines, red, blue, cells, gridReady, connectionLost, gameUuid,
|
||||||
// Setters needed by useServerComm
|
/** Setters needed by useServerComm */
|
||||||
setCells, setGridReady, setGameUuid,
|
setCells, setGridReady, setGameUuid,
|
||||||
// Refs (needed by useServerComm for async-safe reads)
|
/** Refs (needed by useServerComm for async-safe reads) */
|
||||||
webPlayerRef, activePlayerRef, bombSelectedRef, connectionLostRef, endRef,
|
webPlayerRef, activePlayerRef, bombSelectedRef, connectionLostRef, lastClickedRef, endRef,
|
||||||
// Sync helpers
|
/** Sync helpers */
|
||||||
syncWebPlayer, syncActivePlayer, syncBombSelected, syncConnLost, syncRed, syncBlue,
|
syncWebPlayer, syncActivePlayer, syncBombSelected, syncConnLost, syncRed, syncBlue,
|
||||||
// Game logic called by useServerComm
|
/** Game logic called by useServerComm */
|
||||||
showOverlay, hideOverlay,
|
showOverlay, hideOverlay,
|
||||||
applyRevealedCell, applyStep,
|
applyRevealedCell, applyStep,
|
||||||
makeGameEndIfItEnds, resignProcess,
|
makeGameEndIfItEnds, resignProcess,
|
||||||
// UI action
|
/** UI action */
|
||||||
onBombToggle,
|
onBombToggle,
|
||||||
|
/** Sounds */
|
||||||
|
sounds,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -11,4 +11,4 @@ export { default as useGameRefs } from './useGameRefs';
|
|||||||
export { default as useGameState } from './useGameState';
|
export { default as useGameState } from './useGameState';
|
||||||
export { default as useServerCommunication } from './useServerCommunication';
|
export { default as useServerCommunication } from './useServerCommunication';
|
||||||
export { default as useStepTimer } from './useStepTimer';
|
export { default as useStepTimer } from './useStepTimer';
|
||||||
|
export { default as useGameDataProvider, useLobbyDataProvider } from './useGameDataProvider';
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
/**
|
||||||
|
* 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 { useQuery, useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Game Data Provider Hook
|
||||||
|
* Centralized API communication layer for game-related queries and mutations
|
||||||
|
*/
|
||||||
|
const useGameDataProvider = gameAssoc => {
|
||||||
|
// Queries
|
||||||
|
const connectQuery = useQuery({
|
||||||
|
queryKey: ['game-connect', gameAssoc],
|
||||||
|
queryFn: () => fetch(`/api/game/connect/${gameAssoc}`)
|
||||||
|
.then(r => r.text())
|
||||||
|
.then(b64 => JSON.parse(window.atob(b64))),
|
||||||
|
enabled: false,
|
||||||
|
retry: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mutations
|
||||||
|
const startMutation = useMutation({
|
||||||
|
mutationFn: () => fetch('/api/game/start', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ gameAssoc }),
|
||||||
|
}).then(r => r.json()),
|
||||||
|
});
|
||||||
|
|
||||||
|
const joinMutation = useMutation({
|
||||||
|
mutationFn: () => fetch(`/api/game/join/${gameAssoc}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const stepMutation = useMutation({
|
||||||
|
mutationFn: dataPack => fetch(`/api/game/step/${gameAssoc}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(dataPack),
|
||||||
|
}).then(r => r.json()),
|
||||||
|
});
|
||||||
|
|
||||||
|
const heartbeatMutation = useMutation({
|
||||||
|
mutationFn: color => fetch(`/api/game/heartbeat/${gameAssoc}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ color }),
|
||||||
|
}).then(r => r.json()),
|
||||||
|
});
|
||||||
|
|
||||||
|
const challengeRespondMutation = useMutation({
|
||||||
|
mutationFn: ({ challengerGameAssoc, accepted, targetGameAssoc }) => fetch('/api/game/challenge/respond/' + challengerGameAssoc, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ accepted, targetGameAssoc }),
|
||||||
|
}).then(r => r.json()),
|
||||||
|
});
|
||||||
|
|
||||||
|
const leaveMutation = useMutation({
|
||||||
|
mutationFn: () => fetch(`/api/game/leave/${gameAssoc}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
}).then(r => r.json()),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Queries
|
||||||
|
connectQuery,
|
||||||
|
// Mutations
|
||||||
|
startMutation,
|
||||||
|
joinMutation,
|
||||||
|
stepMutation,
|
||||||
|
heartbeatMutation,
|
||||||
|
challengeRespondMutation,
|
||||||
|
leaveMutation,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lobby Data Provider Hook
|
||||||
|
* Centralized API communication layer for lobby-related queries and mutations
|
||||||
|
*/
|
||||||
|
export const useLobbyDataProvider = () => {
|
||||||
|
const waitingPlayersQuery = useQuery({
|
||||||
|
queryKey: ['game-waiting'],
|
||||||
|
queryFn: () => fetch('/api/game/waiting')
|
||||||
|
.then(r => r.json()),
|
||||||
|
});
|
||||||
|
|
||||||
|
const challengeMutation = useMutation({
|
||||||
|
mutationFn: ({ targetGameAssoc, challengerGameAssoc }) => fetch(`/api/game/challenge/${targetGameAssoc}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ challengerGameAssoc }),
|
||||||
|
}).then(r => r.json()),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Queries
|
||||||
|
waitingPlayersQuery,
|
||||||
|
// Mutations
|
||||||
|
challengeMutation,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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 };
|
||||||
|
};
|
||||||
@@ -8,105 +8,231 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
|
||||||
import { useGame } from '@mine-contexts';
|
import { useGame } from '@mine-contexts';
|
||||||
import { DESC } from '@mine-utils';
|
import { DESC, IMAGES } from '@mine-utils';
|
||||||
import useStepTimer from './useStepTimer';
|
import { ChallengeCountdown, WaitingOverlayContent } from '@mine-components';
|
||||||
import { WaitingOverlayContent } from '@mine-components';
|
import { useGameDataProvider, useStepTimer } from '@mine-hooks';
|
||||||
|
|
||||||
import { ChallengeCountdown } from '@mine-components';
|
const useServerCommunication = (gameAssoc, gameInherited, opponentName, isEnvDev) => {
|
||||||
|
|
||||||
const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
|
|
||||||
const {
|
const {
|
||||||
/** Async-safe refs */
|
/** Async-safe refs */
|
||||||
webPlayerRef, activePlayerRef, bombSelectedRef, connectionLostRef, endRef,
|
webPlayerRef, activePlayerRef, bombSelectedRef, connectionLostRef, lastClickedRef, endRef,
|
||||||
/** State setters */
|
/** State setters */
|
||||||
setGridReady, setGameUuid,
|
setCells, setGridReady, setGameUuid,
|
||||||
/** Sync helpers */
|
/** Sync helpers */
|
||||||
syncWebPlayer, syncActivePlayer, syncBombSelected, syncConnLost, syncRed, syncBlue,
|
syncWebPlayer, syncActivePlayer, syncBombSelected, syncConnLost, syncRed, syncBlue,
|
||||||
/** Game logic */
|
/** Game logic */
|
||||||
showOverlay, hideOverlay,
|
showOverlay, hideOverlay, applyStep, makeGameEndIfItEnds, resignProcess, sounds,
|
||||||
applyRevealedCell, applyStep,
|
|
||||||
makeGameEndIfItEnds, resignProcess,
|
|
||||||
/** Current cells snapshot (for active-check in onClick) */
|
/** Current cells snapshot (for active-check in onClick) */
|
||||||
cells,
|
cells,
|
||||||
} = useGame();
|
} = useGame();
|
||||||
|
|
||||||
|
/** Get all API queries and mutations from data provider */
|
||||||
|
const {
|
||||||
|
connectQuery,
|
||||||
|
startMutation,
|
||||||
|
joinMutation,
|
||||||
|
stepMutation,
|
||||||
|
heartbeatMutation,
|
||||||
|
challengeRespondMutation,
|
||||||
|
leaveMutation,
|
||||||
|
} = useGameDataProvider(gameAssoc);
|
||||||
|
|
||||||
const eventSourceRef = useRef(null);
|
const eventSourceRef = useRef(null);
|
||||||
const rpcUsersRef = useRef(null);
|
const rpcUsersRef = useRef(null);
|
||||||
const stepCacheRef = useRef([]);
|
const stepCacheRef = useRef([]);
|
||||||
|
const lastStepRef = useRef(null);
|
||||||
|
const isGameFinishedRef = useRef(false);
|
||||||
const { getStepElapsed, resetStepTimer, startNewTurn } = useStepTimer();
|
const { getStepElapsed, resetStepTimer, startNewTurn } = useStepTimer();
|
||||||
const isGameRunningRef = useRef(false);
|
const isGameRunningRef = useRef(false);
|
||||||
const lastActivePlayerRef = useRef(null);
|
const lastActivePlayerRef = useRef(null);
|
||||||
|
const heartbeatPubIntervalRef = useRef(null);
|
||||||
|
const opponentLastSeenRef = useRef(0);
|
||||||
|
const isTrueRestoredRef = useRef(false);
|
||||||
|
|
||||||
/** REST mutations / queries */
|
const HEARTBEAT_INTERVAL_MS = 1500;
|
||||||
|
|
||||||
const connectQuery = useQuery({
|
|
||||||
queryKey: ['game-connect', gameAssoc],
|
|
||||||
queryFn: () => fetch('/api/game/connect/' + gameAssoc)
|
|
||||||
.then(r => r.text())
|
|
||||||
.then(b64 => JSON.parse(window.atob(b64))),
|
|
||||||
enabled: false,
|
|
||||||
retry: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const startMutation = useMutation({
|
|
||||||
mutationFn: () => fetch('/api/game/start', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ gameAssoc }),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const joinMutation = useMutation({
|
|
||||||
mutationFn: () => fetch('/api/game/join/' + gameAssoc, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
}).catch(e => isEnvDev && console.error('Join error', e)),
|
|
||||||
});
|
|
||||||
|
|
||||||
const stepMutation = useMutation({
|
|
||||||
mutationFn: dataPack => fetch('/api/game/step/' + gameAssoc, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(dataPack),
|
|
||||||
}).then(r => r.json()),
|
|
||||||
});
|
|
||||||
|
|
||||||
/** Game-start helpers (triggered by server events) */
|
/** Game-start helpers (triggered by server events) */
|
||||||
|
|
||||||
const wInit = (revealedCells = []) => {
|
const wInit = (revealedCells = [], lastStep = {}, gameState = {}, isGameFinished = false) => {
|
||||||
setGridReady(true);
|
/** Detect if this is a restored game */
|
||||||
showOverlay('Choose an opponent!', gameAssoc ? (
|
const isRestoredGame = 0 < revealedCells.length;
|
||||||
|
isTrueRestoredRef.current = isRestoredGame;
|
||||||
|
|
||||||
|
/** Store game finished status */
|
||||||
|
isGameFinishedRef.current = isGameFinished;
|
||||||
|
|
||||||
|
/** Apply game state (points, bonus) immediately for restored games */
|
||||||
|
if (0 < Object.keys(gameState).length) {
|
||||||
|
const {
|
||||||
|
redPoints = 0,
|
||||||
|
bluePoints = 0,
|
||||||
|
redBonusPoints = 0,
|
||||||
|
blueBonusPoints = 0,
|
||||||
|
redBonusStats = {},
|
||||||
|
blueBonusStats = {},
|
||||||
|
} = gameState;
|
||||||
|
syncRed(p => ({
|
||||||
|
...p,
|
||||||
|
mines: redPoints,
|
||||||
|
bonusPoints: redBonusPoints,
|
||||||
|
bonusStats: redBonusStats,
|
||||||
|
}));
|
||||||
|
syncBlue(p => ({
|
||||||
|
...p,
|
||||||
|
mines: bluePoints,
|
||||||
|
bonusPoints: blueBonusPoints,
|
||||||
|
bonusStats: blueBonusStats,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Apply revealed cells immediately (not in setTimeout) */
|
||||||
|
if (0 < revealedCells.length) {
|
||||||
|
setCells(prev => {
|
||||||
|
let next = prev.map(r => [...r]);
|
||||||
|
revealedCells.forEach(({ row, col, value, player }) => {
|
||||||
|
if (next[row][col].active) return;
|
||||||
|
/** Check if this cell is the last step for either player */
|
||||||
|
const isRedLastStep = lastStep.red && lastStep.red.player === player && lastStep.red.row === row && lastStep.red.col === col;
|
||||||
|
const isBlueLastStep = lastStep.blue && lastStep.blue.player === player && lastStep.blue.row === row && lastStep.blue.col === col;
|
||||||
|
const patch = 'm' === value
|
||||||
|
? { currentImage: IMAGES.flag(player), currentObj: 'm', active: true }
|
||||||
|
: { currentImage: value, currentObj: value, active: true };
|
||||||
|
if (isRedLastStep || isBlueLastStep) {
|
||||||
|
patch.lastClickedRed = 'red' === player;
|
||||||
|
patch.lastClickedBlue = 'blue' === player;
|
||||||
|
}
|
||||||
|
next[row][col] = { ...next[row][col], ...patch };
|
||||||
|
});
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update the lastClickedRef so applyStep knows about it */
|
||||||
|
if (lastStep.red) {
|
||||||
|
lastClickedRef.current = {
|
||||||
|
...lastClickedRef.current,
|
||||||
|
red: [lastStep.red.row, lastStep.red.col],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (lastStep.blue) {
|
||||||
|
lastClickedRef.current = {
|
||||||
|
...lastClickedRef.current,
|
||||||
|
blue: [lastStep.blue.row, lastStep.blue.col],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Determine overlay message */
|
||||||
|
let overlayTitle, overlaySubtitle;
|
||||||
|
|
||||||
|
if (isGameFinished) {
|
||||||
|
/** Game is finished - show game over message */
|
||||||
|
const redPoints = gameState.redPoints ?? 0;
|
||||||
|
const bluePoints = gameState.bluePoints ?? 0;
|
||||||
|
const winner = redPoints > bluePoints ? 'Red' : 'Blue';
|
||||||
|
overlayTitle = `${winner} wins the game!`;
|
||||||
|
overlaySubtitle = 'Play again!';
|
||||||
|
/** Mark the game as ended */
|
||||||
|
endRef.current = true;
|
||||||
|
} else if (isRestoredGame) {
|
||||||
|
overlayTitle = 'Waiting for opponent to reconnect...';
|
||||||
|
overlaySubtitle = gameAssoc ? (
|
||||||
<WaitingOverlayContent
|
<WaitingOverlayContent
|
||||||
shareUrl={`${window.location.href}/${gameAssoc}`}
|
shareUrl={`${window.location.origin}/play/${gameAssoc}`}
|
||||||
|
currentGameAssoc={gameAssoc}
|
||||||
|
opponentName={opponentName}
|
||||||
|
inviteOnly
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div style={{ textAlign: 'center', padding: '20px' }}>
|
||||||
|
<p>Waiting for opponent to join...</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
overlayTitle = 'Choose an opponent!';
|
||||||
|
overlaySubtitle = gameAssoc ? (
|
||||||
|
<WaitingOverlayContent
|
||||||
|
shareUrl={`${window.location.origin}/play/${gameAssoc}`}
|
||||||
currentGameAssoc={gameAssoc}
|
currentGameAssoc={gameAssoc}
|
||||||
/>
|
/>
|
||||||
) : '');
|
) : '';
|
||||||
setTimeout(() => revealedCells.forEach(cell => applyRevealedCell(cell, cell.player)), 0);
|
}
|
||||||
|
|
||||||
|
showOverlay(overlayTitle, overlaySubtitle);
|
||||||
|
|
||||||
|
/** Use Promise.resolve to defer setGridReady slightly to ensure overlay is rendered first */
|
||||||
|
Promise.resolve().then(() => setGridReady(true));
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeGameStart = payload => {
|
const makeGameStart = payload => {
|
||||||
syncActivePlayer(1);
|
/** Don't start a finished game */
|
||||||
|
if (isGameFinishedRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** If game is being restored and has a most recent step, determine starter based on that */
|
||||||
|
let starterIsBlue;
|
||||||
|
|
||||||
|
/** lastStepRef contains the single most recent step from the server */
|
||||||
|
if (lastStepRef.current && lastStepRef.current.player) {
|
||||||
|
/** The NEXT player is opposite of who made the last step */
|
||||||
|
starterIsBlue = 'red' === lastStepRef.current.player; // If red played last, blue plays next
|
||||||
|
} else {
|
||||||
|
/** New game: blue always starts */
|
||||||
|
starterIsBlue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const starterColor = starterIsBlue ? 'blue' : 'red';
|
||||||
|
const starterVal = starterIsBlue ? 1 : 0;
|
||||||
|
const starterDesc = starterColor === webPlayerRef.current ? DESC.you : DESC.buddy;
|
||||||
|
syncActivePlayer(starterVal);
|
||||||
syncRed(p => ({
|
syncRed(p => ({
|
||||||
...p,
|
...p,
|
||||||
name: payload.users.red || payload.users.redAnon || p.name,
|
name: payload.users.red || payload.users.redAnon || p.name,
|
||||||
registered: !!payload.users.red,
|
registered: !!payload.users.red,
|
||||||
avatar: payload.users.redAvatar ?? null,
|
avatar: payload.users.redAvatar ?? null,
|
||||||
|
desc: 'red' === starterColor ? starterDesc : '',
|
||||||
|
active: 'red' === starterColor,
|
||||||
}));
|
}));
|
||||||
syncBlue(p => ({
|
syncBlue(p => ({
|
||||||
...p,
|
...p,
|
||||||
name: payload.users.blue || payload.users.blueAnon || p.name,
|
name: payload.users.blue || payload.users.blueAnon || p.name,
|
||||||
registered: !!payload.users.blue,
|
registered: !!payload.users.blue,
|
||||||
avatar: payload.users.blueAvatar ?? null,
|
avatar: payload.users.blueAvatar ?? null,
|
||||||
desc: 'blue' === webPlayerRef.current ? DESC.you : DESC.buddy,
|
desc: 'blue' === starterColor ? starterDesc : '',
|
||||||
active: true,
|
active: 'blue' === starterColor,
|
||||||
}));
|
}));
|
||||||
isGameRunningRef.current = true;
|
isGameRunningRef.current = true;
|
||||||
lastActivePlayerRef.current = 1; // Blue starts
|
lastActivePlayerRef.current = starterVal;
|
||||||
startNewTurn();
|
startNewTurn();
|
||||||
resetStepTimer();
|
resetStepTimer();
|
||||||
|
/**
|
||||||
|
* For a truly restored game, keep the "Waiting for opponent..." overlay
|
||||||
|
* up until we actually see a heartbeat from the other player.
|
||||||
|
*/
|
||||||
|
if (!endRef.current && (!isTrueRestoredRef.current || 0 !== opponentLastSeenRef.current)) {
|
||||||
hideOverlay();
|
hideOverlay();
|
||||||
|
sounds.current.starting.play();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const publishHeartbeat = () => {
|
||||||
|
const me = webPlayerRef.current;
|
||||||
|
if (!me || endRef.current) return;
|
||||||
|
heartbeatMutation.mutate(me);
|
||||||
|
};
|
||||||
|
|
||||||
|
const startHeartbeat = () => {
|
||||||
|
if (heartbeatPubIntervalRef.current) return;
|
||||||
|
publishHeartbeat();
|
||||||
|
heartbeatPubIntervalRef.current = setInterval(publishHeartbeat, HEARTBEAT_INTERVAL_MS);
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopHeartbeat = () => {
|
||||||
|
if (heartbeatPubIntervalRef.current) {
|
||||||
|
clearInterval(heartbeatPubIntervalRef.current);
|
||||||
|
heartbeatPubIntervalRef.current = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Mercure / SSE message handlers */
|
/** Mercure / SSE message handlers */
|
||||||
@@ -132,7 +258,28 @@ const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
|
|||||||
|
|
||||||
const wUnsubscribe = payload => {
|
const wUnsubscribe = payload => {
|
||||||
isEnvDev && console.info(payload.msg);
|
isEnvDev && console.info(payload.msg);
|
||||||
showOverlay('The connection has been lost w/ your friend...', 'Please, restart the game!');
|
const isAuthenticated = '1' === document.getElementById('mine-wrapper')?.dataset.isAuthenticated;
|
||||||
|
const redirectPath = isAuthenticated ? '/profile' : '/';
|
||||||
|
const buttonText = isAuthenticated ? 'My Profile' : 'Homepage';
|
||||||
|
const buttonIcon = isAuthenticated ? 'fa-user' : 'fa-house';
|
||||||
|
|
||||||
|
showOverlay(
|
||||||
|
'The connection has been lost w/ your friend...',
|
||||||
|
(
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '12px', width: '100%' }}>
|
||||||
|
<p style={{ margin: 0 }}>Please, restart the game!</p>
|
||||||
|
<a
|
||||||
|
className="game-overlay-profile"
|
||||||
|
href={redirectPath}
|
||||||
|
title={isAuthenticated ? 'Go to your profile' : 'Go to homepage'}
|
||||||
|
aria-label={isAuthenticated ? 'Go to your profile' : 'Go to homepage'}
|
||||||
|
>
|
||||||
|
<i className={`fa ${buttonIcon}`} />
|
||||||
|
{buttonText}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const wChallenge = payload => {
|
const wChallenge = payload => {
|
||||||
@@ -141,29 +288,31 @@ const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
|
|||||||
|
|
||||||
const handleAccept = () => {
|
const handleAccept = () => {
|
||||||
clearTimeout(declineTimeout);
|
clearTimeout(declineTimeout);
|
||||||
fetch('/api/game/challenge/respond/' + challengerGameAssoc, {
|
challengeRespondMutation.mutate(
|
||||||
method: 'POST',
|
{ challengerGameAssoc, accepted: true, targetGameAssoc: gameAssoc },
|
||||||
headers: { 'Content-Type': 'application/json' },
|
{
|
||||||
body: JSON.stringify({ accepted: true, targetGameAssoc: gameAssoc }),
|
onSuccess: () => {
|
||||||
}).then(() => {
|
|
||||||
showOverlay('Challenge accepted!', 'Waiting for the challenger to join...');
|
showOverlay('Challenge accepted!', 'Waiting for the challenger to join...');
|
||||||
}).catch(() => {});
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDecline = () => {
|
const handleDecline = () => {
|
||||||
clearTimeout(declineTimeout);
|
clearTimeout(declineTimeout);
|
||||||
fetch('/api/game/challenge/respond/' + challengerGameAssoc, {
|
challengeRespondMutation.mutate(
|
||||||
method: 'POST',
|
{ challengerGameAssoc, accepted: false, targetGameAssoc: gameAssoc },
|
||||||
headers: { 'Content-Type': 'application/json' },
|
{
|
||||||
body: JSON.stringify({ accepted: false, targetGameAssoc: gameAssoc }),
|
onSuccess: () => {
|
||||||
}).then(() => {
|
|
||||||
showOverlay('We are waiting for your opponent...', gameAssoc ? (
|
showOverlay('We are waiting for your opponent...', gameAssoc ? (
|
||||||
<WaitingOverlayContent
|
<WaitingOverlayContent
|
||||||
shareUrl={window.location.origin + '/play/' + gameAssoc}
|
shareUrl={`${window.location.origin}/play/${gameAssoc}`}
|
||||||
currentGameAssoc={gameAssoc}
|
currentGameAssoc={gameAssoc}
|
||||||
/>
|
/>
|
||||||
) : '');
|
) : '');
|
||||||
}).catch(() => {});
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
declineTimeout = setTimeout(handleDecline, 30000);
|
declineTimeout = setTimeout(handleDecline, 30000);
|
||||||
@@ -188,8 +337,10 @@ const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
|
|||||||
isEnvDev && console.warn(payload.user + ' stepped to ' + payload.data.coords.join(','));
|
isEnvDev && console.warn(payload.user + ' stepped to ' + payload.data.coords.join(','));
|
||||||
syncBombSelected(payload.data.bomb);
|
syncBombSelected(payload.data.bomb);
|
||||||
|
|
||||||
// Detect if turn switched (other player made a move)
|
/**
|
||||||
// After their move, it's now our turn (or the opposite player's turn)
|
* Detect if turn switched (other player made a move)
|
||||||
|
* After their move, it's now our turn (or the opposite player's turn)
|
||||||
|
*/
|
||||||
if (lastActivePlayerRef.current !== activePlayerRef.current) {
|
if (lastActivePlayerRef.current !== activePlayerRef.current) {
|
||||||
startNewTurn();
|
startNewTurn();
|
||||||
lastActivePlayerRef.current = activePlayerRef.current;
|
lastActivePlayerRef.current = activePlayerRef.current;
|
||||||
@@ -210,6 +361,16 @@ const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
|
|||||||
if (undefined !== payload.type) {
|
if (undefined !== payload.type) {
|
||||||
if ('challenge' === payload.type) wChallenge(payload);
|
if ('challenge' === payload.type) wChallenge(payload);
|
||||||
else if ('challenge-response' === payload.type) wChallengeResponse(payload);
|
else if ('challenge-response' === payload.type) wChallengeResponse(payload);
|
||||||
|
else if ('heartbeat' === payload.type) {
|
||||||
|
const me = webPlayerRef.current;
|
||||||
|
if (me && payload.color && payload.color !== me) {
|
||||||
|
const wasFirst = 0 === opponentLastSeenRef.current;
|
||||||
|
opponentLastSeenRef.current = Date.now();
|
||||||
|
if (wasFirst && isTrueRestoredRef.current && !endRef.current) {
|
||||||
|
hideOverlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,9 +396,9 @@ const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
|
|||||||
const subscriberJwt = wrapper.dataset.mercureSubscriberJwt;
|
const subscriberJwt = wrapper.dataset.mercureSubscriberJwt;
|
||||||
const url = new URL(hubUrl, window.location.origin);
|
const url = new URL(hubUrl, window.location.origin);
|
||||||
|
|
||||||
url.searchParams.append('topic', 'mineseeker/channel/' + gameAssoc);
|
url.searchParams.append('topic', `mineseeker/channel/${gameAssoc}`);
|
||||||
if (subscriberJwt) url.searchParams.append('authorization', subscriberJwt);
|
|
||||||
|
|
||||||
|
if (subscriberJwt) url.searchParams.append('authorization', subscriberJwt);
|
||||||
if (eventSourceRef.current) eventSourceRef.current.close();
|
if (eventSourceRef.current) eventSourceRef.current.close();
|
||||||
|
|
||||||
const es = new EventSource(url.toString());
|
const es = new EventSource(url.toString());
|
||||||
@@ -264,6 +425,7 @@ const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
|
|||||||
openEventSource();
|
openEventSource();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (gameInherited) {
|
if (gameInherited) {
|
||||||
const serverData = await connectQuery.refetch().then(r => {
|
const serverData = await connectQuery.refetch().then(r => {
|
||||||
@@ -278,23 +440,50 @@ const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rpcUsersRef.current = serverData.users;
|
rpcUsersRef.current = serverData.users;
|
||||||
|
lastStepRef.current = serverData.mostRecentStep || null;
|
||||||
|
|
||||||
|
/** Pass game state (points, bonus) to wInit */
|
||||||
|
const gameState = {
|
||||||
|
redPoints: serverData.redPoints ?? 0,
|
||||||
|
bluePoints: serverData.bluePoints ?? 0,
|
||||||
|
redBonusPoints: serverData.redBonusPoints ?? 0,
|
||||||
|
blueBonusPoints: serverData.blueBonusPoints ?? 0,
|
||||||
|
redBonusStats: serverData.redBonusStats ?? {},
|
||||||
|
blueBonusStats: serverData.blueBonusStats ?? {},
|
||||||
|
};
|
||||||
|
const isGameFinished = serverData.gameFinished ?? false;
|
||||||
|
wInit(serverData.revealedCells || [], serverData.lastStep || {}, gameState, isGameFinished);
|
||||||
|
|
||||||
|
/** Open event source after showing overlay */
|
||||||
openEventSource();
|
openEventSource();
|
||||||
wInit(serverData.revealedCells || []);
|
|
||||||
} else {
|
} 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();
|
openEventSource();
|
||||||
wInit();
|
wInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
isEnvDev && console.info('Connection initialised — joining channel');
|
isEnvDev && console.info('Connection initialised — joining channel');
|
||||||
await joinMutation.mutateAsync();
|
await joinMutation.mutateAsync();
|
||||||
|
startHeartbeat();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
isEnvDev && console.error('Connection error', e);
|
isEnvDev && console.error('Connection error', e);
|
||||||
|
showOverlay('Error', 'Connection failed. Please try again.');
|
||||||
setTimeout(() => window.location.reload(), 500);
|
setTimeout(() => window.location.reload(), 500);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
window.addEventListener('pagehide', () => navigator.sendBeacon('/api/game/leave/' + gameAssoc));
|
window.addEventListener('pagehide', () => {
|
||||||
|
leaveMutation.mutate();
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
stopHeartbeat();
|
||||||
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -318,9 +507,11 @@ const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
|
|||||||
try {
|
try {
|
||||||
const result = await stepMutation.mutateAsync(dataPack);
|
const result = await stepMutation.mutateAsync(dataPack);
|
||||||
applyStep(result);
|
applyStep(result);
|
||||||
|
|
||||||
if (result.uuid && !endRef.current) {
|
if (result.uuid && !endRef.current) {
|
||||||
setGameUuid(result.uuid);
|
setGameUuid(result.uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
makeGameEndIfItEnds(result.bluePoints, result.redPoints, false, result.leftMines);
|
makeGameEndIfItEnds(result.bluePoints, result.redPoints, false, result.leftMines);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
isEnvDev && console.error('Step error', e);
|
isEnvDev && console.error('Step error', e);
|
||||||
@@ -330,6 +521,7 @@ const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
|
|||||||
const clickResign = () => {
|
const clickResign = () => {
|
||||||
const color = activePlayerRef.current ? 'blue' : 'red';
|
const color = activePlayerRef.current ? 'blue' : 'red';
|
||||||
const stepElapsed = getStepElapsed(activePlayerRef.current, isGameRunningRef.current);
|
const stepElapsed = getStepElapsed(activePlayerRef.current, isGameRunningRef.current);
|
||||||
|
|
||||||
stepMutation.mutate(
|
stepMutation.mutate(
|
||||||
{ resign: color, stepElapsed },
|
{ resign: color, stepElapsed },
|
||||||
{
|
{
|
||||||
@@ -338,13 +530,15 @@ const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
|
|||||||
resignProcess(webPlayerRef.current, result.uuid);
|
resignProcess(webPlayerRef.current, result.uuid);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const resign = () => {
|
const resign = () => {
|
||||||
const activeColor = activePlayerRef.current ? 'blue' : 'red';
|
const activeColor = activePlayerRef.current ? 'blue' : 'red';
|
||||||
|
|
||||||
if (webPlayerRef.current !== activeColor) return;
|
if (webPlayerRef.current !== activeColor) return;
|
||||||
|
|
||||||
showOverlay('Are u sure u want to resign?!', (
|
showOverlay('Are u sure u want to resign?!', (
|
||||||
<div className="resign">
|
<div className="resign">
|
||||||
<a onClick={clickResign}>Yes</a>
|
<a onClick={clickResign}>Yes</a>
|
||||||
|
|||||||
@@ -10,24 +10,18 @@
|
|||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
|
||||||
const useStepTimer = () => {
|
const useStepTimer = () => {
|
||||||
// Record when the current turn started (timestamp)
|
|
||||||
const turnStartTimeRef = useRef(null);
|
const turnStartTimeRef = useRef(null);
|
||||||
// Flag to track if we've already recorded a turn start
|
|
||||||
const turnStartedRef = useRef(false);
|
const turnStartedRef = useRef(false);
|
||||||
|
|
||||||
const getStepElapsed = (currentActivePlayer, isGameRunning) => {
|
const getStepElapsed = (currentActivePlayer, isGameRunning) => {
|
||||||
// If game not running, return 0
|
|
||||||
if (!isGameRunning) 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) {
|
if (!turnStartedRef.current) {
|
||||||
turnStartTimeRef.current = Date.now();
|
turnStartTimeRef.current = Date.now();
|
||||||
turnStartedRef.current = true;
|
turnStartedRef.current = true;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// After initialization, just calculate elapsed time
|
|
||||||
if (turnStartTimeRef.current) {
|
if (turnStartTimeRef.current) {
|
||||||
return Math.floor((Date.now() - turnStartTimeRef.current) / 1000);
|
return Math.floor((Date.now() - turnStartTimeRef.current) / 1000);
|
||||||
}
|
}
|
||||||
@@ -40,7 +34,6 @@ const useStepTimer = () => {
|
|||||||
turnStartedRef.current = false;
|
turnStartedRef.current = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Call this when we know a turn has actually changed (from server response)
|
|
||||||
const startNewTurn = () => {
|
const startNewTurn = () => {
|
||||||
turnStartTimeRef.current = Date.now();
|
turnStartTimeRef.current = Date.now();
|
||||||
turnStartedRef.current = true;
|
turnStartedRef.current = true;
|
||||||
|
|||||||
@@ -34,9 +34,23 @@ export const IMAGES = {
|
|||||||
bombPos: (hor, vert) => `${IMG}bg-bomb-${hor}-${vert}-outbg.png`,
|
bombPos: (hor, vert) => `${IMG}bg-bomb-${hor}-${vert}-outbg.png`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const BONUS_STATS_DEF = {
|
||||||
|
blindHits: 0, chainBest: 0, chainCurrent: 0, lastMineHits: 0, edgeMines: 0, biggestReveal: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BONUS_LABELS = {
|
||||||
|
blindHits: { label: 'Blind hits', desc: 'Mines clicked with no revealed number nearby' },
|
||||||
|
chainBest: { label: 'Best chain', desc: 'Longest streak of consecutive mine-clicks' },
|
||||||
|
chainCurrent: { label: 'Current chain', desc: 'Active consecutive mine-click streak' },
|
||||||
|
lastMineHits: { label: 'Endgame mines', desc: 'Mines clicked while few remain on the board' },
|
||||||
|
edgeMines: { label: 'Edge mines', desc: 'Mines clicked on the board boundary' },
|
||||||
|
biggestReveal: { label: 'Biggest reveal', desc: 'Largest number of safe cells revealed in one click' },
|
||||||
|
};
|
||||||
|
|
||||||
export const PLAYER_DEF = {
|
export const PLAYER_DEF = {
|
||||||
name: '...', desc: '', active: false, mines: 0, haveBomb: true, enabledBomb: true,
|
name: '...', desc: '', active: false, mines: 0, haveBomb: true, enabledBomb: true,
|
||||||
registered: false, avatar: null,
|
registered: false, avatar: null,
|
||||||
|
bonusPoints: 0, bonusStats: { ...BONUS_STATS_DEF },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DESC = {
|
export const DESC = {
|
||||||
|
|||||||
@@ -7,4 +7,4 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { DESC, IMAGES, BOMB_SYMBOLS, PLAYER_DEF, bombRadius, initCells, patchCells } from './constants';
|
export { DESC, IMAGES, BOMB_SYMBOLS, PLAYER_DEF, BONUS_STATS_DEF, BONUS_LABELS, bombRadius, initCells, patchCells } from './constants';
|
||||||
|
|||||||
@@ -9,8 +9,7 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import PasskeyManager from './components/PasskeyManager';
|
import { PasskeyLogin, PasskeyManager } from '@global-components';
|
||||||
import PasskeyLogin from './components/PasskeyLogin';
|
|
||||||
|
|
||||||
const passkeyManagerRoot = document.getElementById('passkey-manager-root');
|
const passkeyManagerRoot = document.getElementById('passkey-manager-root');
|
||||||
|
|
||||||
|
|||||||
+41
-4
@@ -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 React from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import ProfileCharts from './components/ProfileCharts';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import BattleDialog from './components/BattleDialog';
|
import { AvatarUpload, BattleDialog, ProfileCharts } from '@global-components';
|
||||||
import AvatarUpload from './components/AvatarUpload';
|
|
||||||
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
const avatarRoot = document.getElementById('profile-avatar-root');
|
const avatarRoot = document.getElementById('profile-avatar-root');
|
||||||
if (avatarRoot) {
|
if (avatarRoot) {
|
||||||
const { uploadUrl, thumbUrl, initials } = avatarRoot.dataset;
|
const { uploadUrl, thumbUrl, initials } = avatarRoot.dataset;
|
||||||
createRoot(avatarRoot).render(
|
createRoot(avatarRoot).render(
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
<AvatarUpload
|
<AvatarUpload
|
||||||
uploadUrl={uploadUrl}
|
uploadUrl={uploadUrl}
|
||||||
initialThumbUrl={thumbUrl || null}
|
initialThumbUrl={thumbUrl || null}
|
||||||
initials={initials}
|
initials={initials}
|
||||||
/>,
|
/>
|
||||||
|
</QueryClientProvider>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,3 +41,28 @@ if (battleRoot) {
|
|||||||
<BattleDialog games={JSON.parse(battleRoot.dataset.games)} />,
|
<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": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@fontsource/changa-one": "^5.2.8",
|
"@fontsource/changa-one": "^5.3.0",
|
||||||
"@fontsource/open-sans": "^5.2.7",
|
"@fontsource/open-sans": "^5.3.0",
|
||||||
"@fontsource/rajdhani": "^5.2.7",
|
"@fontsource/rajdhani": "^5.3.0",
|
||||||
"@fortawesome/fontawesome-free": "^7.2.0",
|
"@fortawesome/fontawesome-free": "^7.3.1",
|
||||||
"@mui/material": "^9.0.0",
|
"@mui/material": "^9.2.0",
|
||||||
"@mui/x-charts": "^9.0.1",
|
"@mui/x-charts": "^9.10.1",
|
||||||
"@tanstack/react-query": "^5.0.0",
|
"@tanstack/react-query": "^5.101.4",
|
||||||
"howler": "^2.1.2",
|
"cap-widget": "^0.1.56",
|
||||||
|
"howler": "^2.2.4",
|
||||||
"lodash": "^4.18.1",
|
"lodash": "^4.18.1",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.2.8",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.2.8",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.3.5",
|
"@eslint/eslintrc": "^3.3.6",
|
||||||
"@eslint/js": "^9.0.0",
|
"@eslint/js": "10.0.1",
|
||||||
"@stylistic/eslint-plugin": "^4.0.0",
|
"@stylistic/eslint-plugin": "5.10.0",
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^6.0.4",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^10.8.0",
|
||||||
"eslint-plugin-react": "^7.0.0",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^5.0.0",
|
"eslint-plugin-react-hooks": "7.1.1",
|
||||||
"globals": "^15.0.0",
|
"globals": "^17.8.0",
|
||||||
"sass": "^1.77.0",
|
"sass": "^1.102.0",
|
||||||
"vite": "^8.0.8",
|
"vite": "^8.1.5",
|
||||||
"vite-plugin-symfony": "^8.2.4",
|
"vite-plugin-symfony": "^8.2.4",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -38,16 +39,28 @@
|
|||||||
"packages": {
|
"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/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", "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", "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/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", "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-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-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", "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-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-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", "http://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||||
|
|
||||||
|
"@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=="],
|
"@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=="],
|
||||||
|
|
||||||
"@babel/runtime": ["@babel/runtime@7.29.2", "http://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
|
"@babel/runtime": ["@babel/runtime@7.29.2", "http://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
|
||||||
@@ -58,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=="],
|
"@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=="],
|
"@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=="],
|
||||||
|
|
||||||
"@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=="],
|
|
||||||
|
|
||||||
"@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=="],
|
"@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=="],
|
||||||
|
|
||||||
@@ -94,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-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.21.2", "http://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="],
|
"@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.4.2", "http://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
|
"@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@0.17.0", "http://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
|
"@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@9.39.4", "http://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", {}, "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw=="],
|
"@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@2.1.7", "http://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
|
"@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.4.1", "http://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
|
"@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", "", {}, "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=="],
|
"@humanfs/core": ["@humanfs/core@0.19.1", "http://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
||||||
|
|
||||||
@@ -126,35 +137,35 @@
|
|||||||
|
|
||||||
"@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/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", "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=="],
|
"@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=="],
|
||||||
|
|
||||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "http://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "http://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||||
|
|
||||||
"@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=="],
|
"@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.1", "http://registry.npmjs.org/@mui/x-charts/-/x-charts-9.0.1.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.0", "@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-0LyhlGhUm07wGJY0d0U+hSljGS1EHKWgPBsTJ/lBNGDrNc4DI9zSbp4h802LN/eLwMUVXJSI7DH2W3Ef3WsqnQ=="],
|
"@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.0", "http://registry.npmjs.org/@mui/x-internal-gestures/-/x-internal-gestures-9.0.0.tgz", { "dependencies": { "@babel/runtime": "^7.28.6" } }, "sha512-+fW1EUai25GJbivGRsi3GX4GYsSvzFPvUEjmMgB4POkRBDjrEZNaLdVWfapT6DlWv/Vfbi08bYSuyvhPXGMZjw=="],
|
"@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=="],
|
"@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=="],
|
||||||
|
|
||||||
"@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=="],
|
|
||||||
|
|
||||||
"@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=="],
|
"@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=="],
|
||||||
|
|
||||||
@@ -162,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=="],
|
"@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=="],
|
"@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=="],
|
||||||
|
|
||||||
@@ -196,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=="],
|
"@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@4.4.1", "http://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.4.1.tgz", { "dependencies": { "@typescript-eslint/utils": "^8.32.1", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "estraverse": "^5.3.0", "picomatch": "^4.0.2" }, "peerDependencies": { "eslint": ">=9.0.0" } }, "sha512-CEigAk7eOLyHvdgmpZsKFwtiqS2wFwI1fn4j09IU9GmD4euFM4jEBAViWeCqaNLlbX2k2+A/Fq9cje4HQBXuJQ=="],
|
"@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.97.0", "http://registry.npmjs.org/@tanstack/query-core/-/query-core-5.97.0.tgz", {}, "sha512-QdpLP5VzVMgo4VtaPppRA2W04UFjIqX+bxke/ZJhE5cfd5UPkRzqIAJQt9uXkQJjqE8LBOMbKv7f8HCsZltXlg=="],
|
"@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=="],
|
||||||
|
|
||||||
"@tanstack/react-query": ["@tanstack/react-query@5.97.0", "http://registry.npmjs.org/@tanstack/react-query/-/react-query-5.97.0.tgz", { "dependencies": { "@tanstack/query-core": "5.97.0" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-y4So4eGcQoK2WVMAcDNZE9ofB/p5v1OlKvtc1F3uqHwrtifobT7q+ZnXk2mRkc8E84HKYSlAE9z6HXl2V0+ySQ=="],
|
|
||||||
|
|
||||||
"@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=="],
|
|
||||||
|
|
||||||
"@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=="],
|
"@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=="],
|
||||||
|
|
||||||
@@ -242,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-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-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=="],
|
"@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=="],
|
||||||
@@ -256,8 +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/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", "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/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/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=="],
|
"@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=="],
|
||||||
@@ -268,21 +281,9 @@
|
|||||||
|
|
||||||
"@types/react-transition-group": ["@types/react-transition-group@4.4.12", "http://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", { "peerDependencies": { "@types/react": "*" } }, "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w=="],
|
"@types/react-transition-group": ["@types/react-transition-group@4.4.12", "http://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", { "peerDependencies": { "@types/react": "*" } }, "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w=="],
|
||||||
|
|
||||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.58.1", "http://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.1.tgz", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.58.1", "@typescript-eslint/types": "^8.58.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g=="],
|
|
||||||
|
|
||||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.1", "http://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.1.tgz", { "dependencies": { "@typescript-eslint/types": "8.58.1", "@typescript-eslint/visitor-keys": "8.58.1" } }, "sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w=="],
|
|
||||||
|
|
||||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.58.1", "http://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.1.tgz", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw=="],
|
|
||||||
|
|
||||||
"@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=="],
|
"@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=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.58.1", "http://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.1.tgz", { "dependencies": { "@typescript-eslint/project-service": "8.58.1", "@typescript-eslint/tsconfig-utils": "8.58.1", "@typescript-eslint/types": "8.58.1", "@typescript-eslint/visitor-keys": "8.58.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg=="],
|
"@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=="],
|
||||||
|
|
||||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.58.1", "http://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.1.tgz", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.1", "@typescript-eslint/types": "8.58.1", "@typescript-eslint/typescript-estree": "8.58.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ=="],
|
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.1", "http://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.1.tgz", { "dependencies": { "@typescript-eslint/types": "8.58.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ=="],
|
|
||||||
|
|
||||||
"@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=="],
|
|
||||||
|
|
||||||
"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=="],
|
"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=="],
|
||||||
|
|
||||||
@@ -290,8 +291,6 @@
|
|||||||
|
|
||||||
"ajv": ["ajv@6.14.0", "http://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
|
"ajv": ["ajv@6.14.0", "http://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
|
||||||
|
|
||||||
"ansi-styles": ["ansi-styles@4.3.0", "http://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
|
||||||
|
|
||||||
"argparse": ["argparse@2.0.1", "http://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
"argparse": ["argparse@2.0.1", "http://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||||
|
|
||||||
"array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "http://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="],
|
"array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "http://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="],
|
||||||
@@ -316,12 +315,16 @@
|
|||||||
|
|
||||||
"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=="],
|
"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=="],
|
||||||
|
|
||||||
"bezier-easing": ["bezier-easing@2.1.0", "http://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz", {}, "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig=="],
|
"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@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=="],
|
"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=="],
|
"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", "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=="],
|
"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=="],
|
||||||
|
|
||||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "http://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "http://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||||
@@ -330,19 +333,17 @@
|
|||||||
|
|
||||||
"callsites": ["callsites@3.1.0", "http://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
"callsites": ["callsites@3.1.0", "http://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||||
|
|
||||||
"chalk": ["chalk@4.1.2", "http://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
"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=="],
|
"clsx": ["clsx@2.1.1", "http://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||||
|
|
||||||
"color-convert": ["color-convert@2.0.1", "http://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
|
||||||
|
|
||||||
"color-name": ["color-name@1.1.4", "http://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
|
||||||
|
|
||||||
"concat-map": ["concat-map@0.0.1", "http://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
"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@1.9.0", "http://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
|
"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=="],
|
"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=="],
|
||||||
|
|
||||||
@@ -356,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-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-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=="],
|
"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=="],
|
||||||
@@ -392,6 +395,8 @@
|
|||||||
|
|
||||||
"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=="],
|
"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", "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=="],
|
"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=="],
|
||||||
|
|
||||||
"es-abstract": ["es-abstract@1.24.2", "http://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg=="],
|
"es-abstract": ["es-abstract@1.24.2", "http://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg=="],
|
||||||
@@ -410,15 +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=="],
|
"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", "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=="],
|
"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@9.39.4", "http://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="],
|
"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": ["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@5.2.0", "http://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="],
|
"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@8.4.0", "http://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
|
"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=="],
|
"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=="],
|
||||||
|
|
||||||
@@ -454,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=="],
|
"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=="],
|
"flatted": ["flatted@3.4.2", "http://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="],
|
||||||
|
|
||||||
@@ -470,6 +477,8 @@
|
|||||||
|
|
||||||
"generator-function": ["generator-function@2.0.1", "http://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="],
|
"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", "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=="],
|
"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=="],
|
||||||
|
|
||||||
"get-proto": ["get-proto@1.0.1", "http://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
"get-proto": ["get-proto@1.0.1", "http://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||||
@@ -478,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=="],
|
"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@15.15.0", "http://registry.npmjs.org/globals/-/globals-15.15.0.tgz", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="],
|
"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=="],
|
"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=="],
|
||||||
|
|
||||||
@@ -486,8 +495,6 @@
|
|||||||
|
|
||||||
"has-bigints": ["has-bigints@1.1.0", "http://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
|
"has-bigints": ["has-bigints@1.1.0", "http://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
|
||||||
|
|
||||||
"has-flag": ["has-flag@4.0.0", "http://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
|
||||||
|
|
||||||
"has-property-descriptors": ["has-property-descriptors@1.0.2", "http://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
|
"has-property-descriptors": ["has-property-descriptors@1.0.2", "http://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
|
||||||
|
|
||||||
"has-proto": ["has-proto@1.2.0", "http://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="],
|
"has-proto": ["has-proto@1.2.0", "http://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="],
|
||||||
@@ -498,9 +505,13 @@
|
|||||||
|
|
||||||
"hasown": ["hasown@2.0.2", "http://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
"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", "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", "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=="],
|
"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=="],
|
||||||
|
|
||||||
"howler": ["howler@2.1.2", "http://registry.npmjs.org/howler/-/howler-2.1.2.tgz", {}, "sha512-oKrTFaVXsDRoB/jik7cEpWKTj7VieoiuzMYJ7E/EU5ayvmpRhumCv3YQ3823zi9VTJkSWAhbryHnlZAionGAJg=="],
|
"howler": ["howler@2.2.4", "http://registry.npmjs.org/howler/-/howler-2.2.4.tgz", {}, "sha512-iARIBPgcQrwtEr+tALF+rapJ8qSc+Set2GJQl7xT1MQzWaVkFebdJhR3alVlSiUf5U7nAANKuj3aWpwerocD5w=="],
|
||||||
|
|
||||||
"ignore": ["ignore@5.3.2", "http://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
"ignore": ["ignore@5.3.2", "http://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||||
|
|
||||||
@@ -574,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-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=="],
|
"jsesc": ["jsesc@3.1.0", "http://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||||
|
|
||||||
@@ -586,35 +597,37 @@
|
|||||||
|
|
||||||
"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=="],
|
"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", "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=="],
|
"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=="],
|
||||||
|
|
||||||
"keyv": ["keyv@4.5.4", "http://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
"keyv": ["keyv@4.5.4", "http://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
||||||
|
|
||||||
"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=="],
|
"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=="],
|
"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=="],
|
||||||
|
|
||||||
@@ -622,10 +635,10 @@
|
|||||||
|
|
||||||
"lodash": ["lodash@4.18.1", "http://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="],
|
"lodash": ["lodash@4.18.1", "http://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="],
|
||||||
|
|
||||||
"lodash.merge": ["lodash.merge@4.6.2", "http://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
|
||||||
|
|
||||||
"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=="],
|
"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", "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=="],
|
"math-intrinsics": ["math-intrinsics@1.1.0", "http://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||||
|
|
||||||
"merge2": ["merge2@1.4.1", "http://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
"merge2": ["merge2@1.4.1", "http://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
||||||
@@ -638,7 +651,7 @@
|
|||||||
|
|
||||||
"ms": ["ms@2.1.3", "http://registry.npmjs.org/ms/-/ms-2.1.3.tgz", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
"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=="],
|
"natural-compare": ["natural-compare@1.4.0", "http://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||||
|
|
||||||
@@ -646,6 +659,8 @@
|
|||||||
|
|
||||||
"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-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", "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=="],
|
"object-assign": ["object-assign@4.1.1", "http://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||||
|
|
||||||
"object-inspect": ["object-inspect@1.13.4", "http://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
"object-inspect": ["object-inspect@1.13.4", "http://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||||
@@ -686,31 +701,31 @@
|
|||||||
|
|
||||||
"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=="],
|
"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=="],
|
"prelude-ls": ["prelude-ls@1.2.1", "http://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||||
|
|
||||||
"prop-types": ["prop-types@15.7.2", "http://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.8.1" } }, "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ=="],
|
"prop-types": ["prop-types@15.8.1", "http://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
||||||
|
|
||||||
"punycode": ["punycode@2.3.1", "http://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
"punycode": ["punycode@2.3.1", "http://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||||
|
|
||||||
"queue-microtask": ["queue-microtask@1.2.3", "http://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
"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=="],
|
"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=="],
|
"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=="],
|
"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=="],
|
"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=="],
|
||||||
|
|
||||||
@@ -718,7 +733,7 @@
|
|||||||
|
|
||||||
"reusify": ["reusify@1.1.0", "http://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
"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=="],
|
"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=="],
|
||||||
|
|
||||||
@@ -728,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=="],
|
"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=="],
|
"scheduler": ["scheduler@0.27.0", "http://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||||
|
|
||||||
@@ -774,20 +789,14 @@
|
|||||||
|
|
||||||
"stylis": ["stylis@4.2.0", "http://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", {}, "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="],
|
"stylis": ["stylis@4.2.0", "http://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", {}, "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="],
|
||||||
|
|
||||||
"supports-color": ["supports-color@7.2.0", "http://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
|
||||||
|
|
||||||
"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=="],
|
"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=="],
|
"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=="],
|
"totalist": ["totalist@3.0.1", "http://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
|
||||||
|
|
||||||
"ts-api-utils": ["ts-api-utils@2.5.0", "http://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
|
|
||||||
|
|
||||||
"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=="],
|
"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=="],
|
"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=="],
|
||||||
@@ -798,15 +807,15 @@
|
|||||||
|
|
||||||
"typed-array-length": ["typed-array-length@1.0.7", "http://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="],
|
"typed-array-length": ["typed-array-length@1.0.7", "http://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="],
|
||||||
|
|
||||||
"typescript": ["typescript@6.0.2", "http://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="],
|
|
||||||
|
|
||||||
"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=="],
|
"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", "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=="],
|
"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=="],
|
"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=="],
|
"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=="],
|
||||||
|
|
||||||
@@ -822,35 +831,53 @@
|
|||||||
|
|
||||||
"word-wrap": ["word-wrap@1.2.5", "http://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
"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", "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=="],
|
"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=="],
|
"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", "http://registry.npmjs.org/zod/-/zod-4.3.6.tgz", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "http://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "http://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||||
|
|
||||||
|
"@eslint/config-array/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=="],
|
||||||
|
|
||||||
"@eslint/eslintrc/globals": ["globals@14.0.0", "http://registry.npmjs.org/globals/-/globals-14.0.0.tgz", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
|
"@eslint/eslintrc/globals": ["globals@14.0.0", "http://registry.npmjs.org/globals/-/globals-14.0.0.tgz", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
|
||||||
|
|
||||||
"@mui/material/prop-types": ["prop-types@15.8.1", "http://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
"@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/prop-types": ["prop-types@15.8.1", "http://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
"@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/prop-types": ["prop-types@15.8.1", "http://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
"@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/prop-types": ["prop-types@15.8.1", "http://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
"@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/utils/prop-types": ["prop-types@15.8.1", "http://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
"@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/x-charts/prop-types": ["prop-types@15.8.1", "http://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
"@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=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/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=="],
|
"@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=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "http://registry.npmjs.org/semver/-/semver-7.7.4.tgz", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
"@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=="],
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys/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=="],
|
"@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=="],
|
"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-plugin-react/prop-types": ["prop-types@15.8.1", "http://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
"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", "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=="],
|
||||||
|
|
||||||
"fast-glob/glob-parent": ["glob-parent@5.1.2", "http://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
"fast-glob/glob-parent": ["glob-parent@5.1.2", "http://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||||
|
|
||||||
@@ -858,30 +885,16 @@
|
|||||||
|
|
||||||
"micromatch/picomatch": ["picomatch@2.3.2", "http://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
"micromatch/picomatch": ["picomatch@2.3.2", "http://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||||
|
|
||||||
"prop-types/react-is": ["react-is@16.11.0", "http://registry.npmjs.org/react-is/-/react-is-16.11.0.tgz", {}, "sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw=="],
|
"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=="],
|
||||||
|
|
||||||
"react-transition-group/prop-types": ["prop-types@15.8.1", "http://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
"vite/picomatch": ["picomatch@4.0.5", "http://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="],
|
||||||
|
|
||||||
"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=="],
|
"@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=="],
|
||||||
|
|
||||||
"@mui/material/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=="],
|
"eslint/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=="],
|
||||||
|
|
||||||
"@mui/private-theming/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=="],
|
"@eslint/config-array/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "http://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||||
|
|
||||||
"@mui/styled-engine/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=="],
|
"eslint/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "http://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||||
|
|
||||||
"@mui/system/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=="],
|
|
||||||
|
|
||||||
"@mui/utils/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=="],
|
|
||||||
|
|
||||||
"@mui/x-charts/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=="],
|
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/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=="],
|
|
||||||
|
|
||||||
"eslint-plugin-react/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=="],
|
|
||||||
|
|
||||||
"react-transition-group/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=="],
|
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "http://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+128
-17
@@ -6,11 +6,14 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
container_name: '${APP_NAME}-app'
|
container_name: '${APP_NAME}-app'
|
||||||
ports:
|
ports:
|
||||||
- "10080:80"
|
- "${APP_PUBLISHED_PORT:-10080}:80"
|
||||||
environment:
|
environment:
|
||||||
SERVER_NAME: ${SERVER_NAME:-:80}
|
SERVER_NAME: ${SERVER_NAME:-:80}
|
||||||
APP_ENV: prod
|
APP_ENV: prod
|
||||||
|
APP_VERSION: ${APP_VERSION:-1.0.0}
|
||||||
APP_SECRET: ${APP_SECRET}
|
APP_SECRET: ${APP_SECRET}
|
||||||
|
APP_PUBLIC_HOSTNAME: ${APP_PUBLIC_HOSTNAME:-localhost}
|
||||||
|
APP_CONTACT_MAIL_ADDRESS: ${APP_CONTACT_MAIL_ADDRESS:-7system7@gmail.com}
|
||||||
DATABASE_URL: >-
|
DATABASE_URL: >-
|
||||||
postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?serverVersion=${POSTGRES_VERSION}&charset=utf8
|
postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?serverVersion=${POSTGRES_VERSION}&charset=utf8
|
||||||
POSTGRES_URL: db
|
POSTGRES_URL: db
|
||||||
@@ -23,31 +26,130 @@ services:
|
|||||||
MERCURE_JWT_TOKEN: ${MERCURE_JWT_TOKEN}
|
MERCURE_JWT_TOKEN: ${MERCURE_JWT_TOKEN}
|
||||||
MERCURE_SUBSCRIBER_JWT: ${MERCURE_SUBSCRIBER_JWT}
|
MERCURE_SUBSCRIBER_JWT: ${MERCURE_SUBSCRIBER_JWT}
|
||||||
MAILER_DSN: smtp://mail:25?verify_peer=0
|
MAILER_DSN: smtp://mail:25?verify_peer=0
|
||||||
RECAPTCHA_SITE_KEY: ${RECAPTCHA_SITE_KEY}
|
CAP_API_ENDPOINT: ${CAP_API_ENDPOINT}
|
||||||
RECAPTCHA_SECRET_KEY: ${RECAPTCHA_SECRET_KEY}
|
CAP_SECRET_KEY: ${CAP_SECRET_KEY}
|
||||||
WEBAUTHN_RP_ID: ${WEBAUTHN_RP_ID:-localhost}
|
WEBAUTHN_RP_ID: ${WEBAUTHN_RP_ID:-localhost}
|
||||||
WEBAUTHN_ORIGIN: ${WEBAUTHN_ORIGIN:-https://localhost}
|
WEBAUTHN_ORIGIN: ${WEBAUTHN_ORIGIN:-https://localhost}
|
||||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||||
MINIO_ENDPOINT: http://minio:9000
|
MINIO_ENDPOINT: http://rustfs:9000
|
||||||
MINIO_PUBLIC_URL: ${MINIO_PUBLIC_URL:-http://localhost: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.
|
||||||
|
# WARNING: Using 0.0.0.0/0 is insecure in production environments!
|
||||||
|
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.1}
|
||||||
volumes:
|
volumes:
|
||||||
- app_var:/app/var
|
- app_var:/app/var
|
||||||
- caddy_data:/data
|
- caddy_data:/data
|
||||||
- caddy_config:/config
|
- 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:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
mail:
|
mail:
|
||||||
condition: service_started
|
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:
|
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
|
restart: unless-stopped
|
||||||
container_name: '${APP_NAME}-minio'
|
container_name: '${APP_NAME}-minio'
|
||||||
command: server /data --console-address ":9001"
|
command: server /data --console-address ":9001"
|
||||||
ports:
|
ports:
|
||||||
- "9000:9000"
|
- "${MINIO_PUBLISHED_API_PORT:-9012}:9000"
|
||||||
- "9001:9001"
|
- "${MINIO_PUBLISHED_CONSOLE_PORT:-9013}:9001"
|
||||||
environment:
|
environment:
|
||||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||||
@@ -59,19 +161,25 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
start_period: 10s
|
start_period: 10s
|
||||||
minio_init:
|
storage_migrate:
|
||||||
image: minio/minio:RELEASE.2025-09-07T16-13-09Z-cpuv1
|
profiles: ["storage-migration"]
|
||||||
|
build:
|
||||||
|
context: ./docker/storage-migrate
|
||||||
restart: "no"
|
restart: "no"
|
||||||
container_name: '${APP_NAME}-minio-init'
|
container_name: '${APP_NAME}-storage-migrate'
|
||||||
depends_on:
|
depends_on:
|
||||||
minio:
|
minio:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
rustfs:
|
||||||
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
SOURCE_ENDPOINT: http://minio:9000
|
||||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
TARGET_ENDPOINT: http://rustfs:9000
|
||||||
|
STORAGE_ACCESS_KEY: ${MINIO_ROOT_USER}
|
||||||
|
STORAGE_SECRET_KEY: ${MINIO_ROOT_PASSWORD}
|
||||||
|
STORAGE_BUCKET: mineseeker
|
||||||
volumes:
|
volumes:
|
||||||
- ./docker/minio-init.sh:/minio-init.sh:ro
|
- storage_migration_state:/state
|
||||||
entrypoint: ["/bin/sh", "/minio-init.sh"]
|
|
||||||
mail:
|
mail:
|
||||||
image: boky/postfix:latest
|
image: boky/postfix:latest
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -88,6 +196,7 @@ services:
|
|||||||
RELAYHOST_PASSWORD: ${MAIL_RELAYHOST_PASSWORD:-}
|
RELAYHOST_PASSWORD: ${MAIL_RELAYHOST_PASSWORD:-}
|
||||||
volumes:
|
volumes:
|
||||||
- postfix_spool:/var/spool/postfix
|
- postfix_spool:/var/spool/postfix
|
||||||
|
- ./docker/aliases:/tmp/aliases:ro
|
||||||
db:
|
db:
|
||||||
image: postgres:${POSTGRES_VERSION:-18}-alpine
|
image: postgres:${POSTGRES_VERSION:-18}-alpine
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -97,7 +206,7 @@ services:
|
|||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}" ]
|
test: [ "CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}" ]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
@@ -105,7 +214,7 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
start_period: 10s
|
start_period: 10s
|
||||||
ports:
|
ports:
|
||||||
- "15432:5432"
|
- "${POSTGRES_PUBLISHED_PORT:-5452}:5432"
|
||||||
volumes:
|
volumes:
|
||||||
app_var:
|
app_var:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
@@ -113,3 +222,5 @@ volumes:
|
|||||||
caddy_config:
|
caddy_config:
|
||||||
postfix_spool:
|
postfix_spool:
|
||||||
minio_data:
|
minio_data:
|
||||||
|
rustfs_data:
|
||||||
|
storage_migration_state:
|
||||||
|
|||||||
+40
-25
@@ -1,16 +1,25 @@
|
|||||||
{
|
{
|
||||||
|
"name": "splendid-bear/mineseeker",
|
||||||
|
"version": "2026.3.0",
|
||||||
|
"license": "GPL-3.0-or-later",
|
||||||
|
"author": "https://www.splendidbear.org",
|
||||||
|
"bugs": "https://source.splendidbear.org",
|
||||||
|
"description": "This is a minesweeper game that is inspired from MSN Messenger's game.",
|
||||||
|
"minimum-stability": "dev",
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"license": "proprietary",
|
"prefer-stable": true,
|
||||||
|
"private": true,
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=8.5",
|
"php": ">=8.5.12",
|
||||||
|
"ext-gd": "*",
|
||||||
"ext-iconv": "*",
|
"ext-iconv": "*",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-gd": "*",
|
"doctrine/dbal": "^4.3",
|
||||||
"doctrine/dbal": "^3.7",
|
"doctrine/doctrine-bundle": "^3.2",
|
||||||
"doctrine/doctrine-bundle": ">=2.11 <2.14",
|
"doctrine/doctrine-migrations-bundle": "^4.0",
|
||||||
"doctrine/doctrine-migrations-bundle": "^3.0",
|
"doctrine/orm": "^3.5",
|
||||||
"doctrine/orm": "^2.6",
|
|
||||||
"endroid/qr-code": "^6.1",
|
"endroid/qr-code": "^6.1",
|
||||||
|
"firebase/php-jwt": "^7.0",
|
||||||
"league/flysystem-aws-s3-v3": "^3.0",
|
"league/flysystem-aws-s3-v3": "^3.0",
|
||||||
"league/flysystem-bundle": "^3.6",
|
"league/flysystem-bundle": "^3.6",
|
||||||
"liip/imagine-bundle": "^2.13",
|
"liip/imagine-bundle": "^2.13",
|
||||||
@@ -18,29 +27,35 @@
|
|||||||
"scheb/2fa-backup-code": "^8.5",
|
"scheb/2fa-backup-code": "^8.5",
|
||||||
"scheb/2fa-bundle": "^8.5",
|
"scheb/2fa-bundle": "^8.5",
|
||||||
"scheb/2fa-totp": "^8.5",
|
"scheb/2fa-totp": "^8.5",
|
||||||
"symfony/console": "7.4.*",
|
"symfony/console": "8.1.*",
|
||||||
"symfony/flex": "^2.10.0",
|
"symfony/flex": "^2.10.0",
|
||||||
"symfony/form": "7.4.*",
|
"symfony/form": "8.1.*",
|
||||||
"symfony/framework-bundle": "7.4.*",
|
"symfony/framework-bundle": "8.1.*",
|
||||||
"symfony/http-client": "7.4.*",
|
"symfony/http-client": "8.1.*",
|
||||||
"symfony/mailer": "7.4.*",
|
"symfony/mailer": "8.1.*",
|
||||||
"symfony/mercure": "^0.6",
|
"symfony/mercure": "^0.9",
|
||||||
"symfony/mercure-bundle": "*",
|
"symfony/mercure-bundle": "^0.6",
|
||||||
"symfony/monolog-bundle": "^3.8",
|
"symfony/messenger": "8.1.*",
|
||||||
"symfony/security-bundle": "7.4.*",
|
"symfony/monolog-bundle": "^4.0",
|
||||||
"symfony/translation": "7.4.*",
|
"symfony/scheduler": "8.1.*",
|
||||||
"symfony/twig-bundle": "7.4.*",
|
"symfony/security-bundle": "8.1.*",
|
||||||
"symfony/validator": "7.4.*",
|
"symfony/translation": "8.1.*",
|
||||||
"symfony/yaml": "7.4.*",
|
"symfony/twig-bundle": "8.1.*",
|
||||||
|
"symfony/validator": "8.1.*",
|
||||||
|
"symfony/yaml": "8.1.*",
|
||||||
"web-auth/webauthn-framework": "^5.2"
|
"web-auth/webauthn-framework": "^5.2"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"firebase/php-jwt": "^7.0",
|
"dama/doctrine-test-bundle": "^8.6",
|
||||||
|
"phpunit/phpunit": "^13.1",
|
||||||
"roave/security-advisories": "dev-master",
|
"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/maker-bundle": "^1.5",
|
||||||
"symfony/stopwatch": "7.4.*",
|
"symfony/stopwatch": "8.1.*",
|
||||||
"symfony/web-profiler-bundle": "7.4.*"
|
"symfony/web-profiler-bundle": "8.1.*",
|
||||||
|
"zenstruck/foundry": "^2.9"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"preferred-install": {
|
"preferred-install": {
|
||||||
@@ -89,7 +104,7 @@
|
|||||||
"extra": {
|
"extra": {
|
||||||
"symfony": {
|
"symfony": {
|
||||||
"allow-contrib": false,
|
"allow-contrib": false,
|
||||||
"require": "7.4.*"
|
"require": "8.1.*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+4569
-1990
File diff suppressed because it is too large
Load Diff
@@ -16,4 +16,6 @@ return [
|
|||||||
Scheb\TwoFactorBundle\SchebTwoFactorBundle::class => ['all' => true],
|
Scheb\TwoFactorBundle\SchebTwoFactorBundle::class => ['all' => true],
|
||||||
League\FlysystemBundle\FlysystemBundle::class => ['all' => true],
|
League\FlysystemBundle\FlysystemBundle::class => ['all' => true],
|
||||||
Liip\ImagineBundle\LiipImagineBundle::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)%'
|
url: '%env(resolve:DATABASE_URL)%'
|
||||||
|
|
||||||
orm:
|
orm:
|
||||||
auto_generate_proxy_classes: '%kernel.debug%'
|
enable_native_lazy_objects: true
|
||||||
naming_strategy: doctrine.orm.naming_strategy.underscore
|
naming_strategy: doctrine.orm.naming_strategy.underscore
|
||||||
auto_mapping: true
|
auto_mapping: true
|
||||||
|
schema_ignore_classes:
|
||||||
|
- App\Entity\UserStats
|
||||||
|
- App\Entity\RecentBattle
|
||||||
mappings:
|
mappings:
|
||||||
App:
|
App:
|
||||||
is_bundle: false
|
is_bundle: false
|
||||||
|
|||||||
@@ -8,6 +8,13 @@ framework:
|
|||||||
session:
|
session:
|
||||||
handler_id: ~
|
handler_id: ~
|
||||||
|
|
||||||
|
# Trust headers from reverse proxy (Caddy)
|
||||||
|
# This ensures absolute_url() uses HTTPS scheme when behind a reverse proxy
|
||||||
|
# Production: TRUSTED_PROXIES from .env (Gitea secret)
|
||||||
|
# Development: TRUSTED_PROXIES from compose.override.yaml
|
||||||
|
trusted_proxies: '%env(TRUSTED_PROXIES)%'
|
||||||
|
trusted_headers: ['x-forwarded-for', 'x-forwarded-proto', 'x-forwarded-host', 'x-forwarded-port']
|
||||||
|
|
||||||
#esi: true
|
#esi: true
|
||||||
#fragments: true
|
#fragments: true
|
||||||
php_errors:
|
php_errors:
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ mercure:
|
|||||||
default:
|
default:
|
||||||
url: '%env(MERCURE_URL)%'
|
url: '%env(MERCURE_URL)%'
|
||||||
public_url: '%env(MERCURE_PUBLIC_URL)%'
|
public_url: '%env(MERCURE_PUBLIC_URL)%'
|
||||||
|
protocol_version: '0.x'
|
||||||
jwt:
|
jwt:
|
||||||
secret: '%env(MERCURE_JWT_SECRET)%'
|
secret: '%env(MERCURE_JWT_SECRET)%'
|
||||||
publish: '*'
|
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://'
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user