diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f4b785d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,16 @@ +.git +.gitignore +.env +.venv +.vscode +__pycache__/ +*.pyc +*.pyo +*.pyd +.pytest_cache/ +backend/.pytest_cache/ +backend/**/__pycache__/ +backend/.mypy_cache/ +frontend/node_modules/ +frontend/dist/ +frontend/.vite/ diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3027bc5 --- /dev/null +++ b/.env.example @@ -0,0 +1,24 @@ +# Copy this file to `.env` in the repository root (same directory as `docker-compose.yml`). +# Docker Compose reads these values for `${VAR:-default}` substitution. +# The backend loads `Settings` from this path; the frontend reads Vite variables from the parent folder (`frontend/vite.config.ts` sets `envDir` to `..`). + +# --- Backend --- +SECRET_KEY=change-me-in-production +# Local Postgres (matches docker-compose `db` service when using host port 5432): +SQLALCHEMY_DATABASE_URI=postgresql+asyncpg://tournament:tournament@localhost:5432/tournament +# Used by CORS, email-style links, and the SQLAdmin "Back to frontend" menu: +FRONTEND_URL=http://localhost:5173 + +# --- Frontend (Vite) --- +VITE_BACKEND_URL=http://localhost:8000 +VITE_SOCKETIO_SERVER_URL=http://localhost:8000 + +# --- Firebase (same keys are read by backend `Settings` for admin / token flows) --- +# For interactive review you need a real Firebase project. For automated tests, CI uses placeholder strings. +VITE_FIREBASE_API_KEY= +VITE_FIREBASE_AUTH_DOMAIN= +VITE_FIREBASE_PROJECT_ID= +VITE_FIREBASE_STORAGE_BUCKET= +VITE_FIREBASE_MESSAGING_SENDER_ID= +VITE_FIREBASE_APP_ID= +VITE_FIREBASE_MEASUREMENT_ID= diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..79481c3 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,74 @@ +name: Running tests + +on: + push: + branches: [main, dev] + pull_request: + branches: [main, dev] + +jobs: + test-frontend: + runs-on: ubuntu-latest + container: + image: node:20 + strategy: + matrix: + node-version: [20.x] + defaults: + run: + working-directory: ./frontend + env: + VITE_FIREBASE_API_KEY: "test-api-key" + VITE_FIREBASE_AUTH_DOMAIN: "test.firebaseapp.com" + VITE_FIREBASE_PROJECT_ID: "test-project" + VITE_FIREBASE_STORAGE_BUCKET: "test-project.appspot.com" + VITE_FIREBASE_MESSAGING_SENDER_ID: "123456789" + VITE_FIREBASE_APP_ID: "1:123456789:web:abcdef123456" + VITE_FIREBASE_MEASUREMENT_ID: "G-TEST123456" + VITE_BACKEND_URL: "http://localhost:8000" + VITE_SOCKETIO_SERVER_URL: "http://localhost:8000" + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + cache-dependency-path: ./frontend/package-lock.json + node-version: ${{ matrix.node-version }} + cache: 'npm' + - name: Install dependencies + run: npm install + - name: Run vitest + run: npm run test + + test-backend: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.12'] + defaults: + run: + working-directory: ./backend + env: + SQLALCHEMY_DATABASE_URI: "sqlite+aiosqlite:///./test.db" + SECRET_KEY: "test-secret-key-for-ci" + FRONTEND_URL: "http://localhost:5173" + VITE_FIREBASE_API_KEY: "test-api-key" + VITE_FIREBASE_AUTH_DOMAIN: "test.firebaseapp.com" + VITE_FIREBASE_PROJECT_ID: "test-project" + VITE_FIREBASE_STORAGE_BUCKET: "test-project.appspot.com" + VITE_FIREBASE_MESSAGING_SENDER_ID: "123456789" + VITE_FIREBASE_APP_ID: "1:123456789:web:abcdef123456" + VITE_FIREBASE_MEASUREMENT_ID: "G-TEST123456" + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r ../requirements.txt + - name: Run pytest + run: | + pytest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c46b340 --- /dev/null +++ b/.gitignore @@ -0,0 +1,365 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +# Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +# poetry.lock +# poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +# pdm.lock +# pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +# pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# Redis +*.rdb +*.aof +*.pid + +# RabbitMQ +mnesia/ +rabbitmq/ +rabbitmq-data/ + +# ActiveMQ +activemq-data/ + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +# .idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml + + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.* +!.env.example + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist +.output + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Sveltekit cache directory +.svelte-kit/ + +# vitepress build output +**/.vitepress/dist + +# vitepress cache directory +**/.vitepress/cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# Firebase cache directory +.firebase/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# pnpm +.pnpm-store + +# yarn v3 +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# Vite files +vite.config.js.timestamp-* +vite.config.ts.timestamp-* +.vite/ +*.db +serviceAccountKey.json +.vscode \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/Project.iml b/.idea/Project.iml new file mode 100644 index 0000000..24643cc --- /dev/null +++ b/.idea/Project.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..03d9549 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..013246c --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..74f1bcb --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..50c49c5 --- /dev/null +++ b/README.en.md @@ -0,0 +1,326 @@ +

English documentation

+

+ + + +

+ +## Project overview + +This is a full-featured **tournament management** application: organizers can create and manage tournaments, tasks, teams, and news; participants go through a structured workflow; jury members evaluate submissions. The UI is a single-page React app backed by a **FastAPI** service with **real-time notifications** over **Socket.IO**. + +**Main capabilities** + +- Authentication via password or Google OAuth (built with Firebase Auth). +- Browse tournaments, register without prior platform registration, submit round results. +- Tournament organizers can manage tournaments, rounds, jury panels, and evaluation criteria. +- Jury members appointed by organizers have a convenient panel to evaluate user submissions. +- Users can submit organizer role requests for admin review. +- Admin panel built with SQLAdmin for full database management. +- News page on the platform, plus global notification sending capability. +- Config management via `shared/app_config.json` for roles, statuses, categories. + +**Architecture (short)** + +```text +Browser (React + Vite) + │ HTTPS / REST + WebSocket (Socket.IO client) + ▼ +FastAPI + python-socketio ASGI app (`app:socket_app`) + │ SQLAlchemy (async) + Alembic migrations + ▼ +PostgreSQL +``` + +Shared configuration for roles, statuses, and categories lives in `shared/app_config.json` and is read by both the backend (`app.config`) and the frontend (`src/config/appConfig.ts`). +The database is seeded based on this file. It makes it easy to create, delete, and edit statuses, categories, roles, etc. + +--- + +## Tech stack + +| Area | Technologies | +| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Frontend** | React 19, TypeScript, Vite 7, Tailwind CSS 4, Redux Toolkit, TanStack Query, Zod, React Router 7, Socket.IO client, Firebase JS SDK, Vitest, Testing Library | +| **Backend** | Python 3.12, FastAPI, Uvicorn, SQLAlchemy 2 (async), Alembic, Pydantic Settings, python-socketio, Firebase Admin SDK, SQLAdmin | +| **Database** | PostgreSQL 16 (async driver: `asyncpg`; tests use SQLite via `aiosqlite`) | +| **Testing** | Frontend: Vitest (`npm run test`, `npm run test:coverage`). Backend: pytest + pytest-asyncio (`pytest` from `backend/`, coverage: `pytest --cov=app tests`) | +| **Docker / dev** | Docker Compose (`db`, `backend`, `frontend`), multi-stage Dockerfiles under `backend/` and `frontend/` | + +--- + +## Running the project + +## Required steps + +### 1. Clone the repository + +```bash +git clone +cd +``` + +### 2. Environment file + +```bash +cp .env.example .env +``` + +**Creating `.env`** + +```bash +cp .env.example .env +``` + +Edit `.env` and fill in the **Firebase** fields (see [Environment variables](#environment-variables)). The stack can start with empty Firebase strings, but **sign-in and authenticated API calls require real Firebase configuration** and a **service account key** on the backend (see [Notes for reviewers](#notes-for-reviewers)). + +### 3. Firebase (required for authentication) + +Register on the Firebase platform, create a project, and enable password and Google authorization. You also need to create a Service account. Here are the official tutorials explaining how to do this: +https://firebase.google.com/docs/admin/setup#set-up-project-and-service-account +https://firebase.google.com/docs/admin/setup#initialize_the_sdk_in_non-google_environments +Once you have the .json key file, place it at the following path: + +`backend/app/serviceAccountKey.json` +_The path can be changed in config.py if needed_ + +This path is in .gitignore. If you changed the file name, we recommend updating .gitignore too. Download a service account JSON from your Firebase project and save it there. If the file is missing, the backend still starts, but **ID token verification** used by protected routes will not work until a valid key is present. + +## Quick start (recommended): Docker Compose + +This is the fastest way for developers to run **Postgres + API + Vite dev server** together. + +### Prerequisites + +- [Docker](https://docs.docker.com/get-docker/) and Docker Compose v2 (`docker compose`). + +### Start all services + +From the **repository root** (where `docker-compose.yml` is): + +```bash +docker compose up --build -d +``` + +Wait until the database health check passes and the backend finishes `python -m app.init_db` before using the app. + +### Rebuild after dependency changes + +```bash +docker compose build --no-cache +docker compose up -d +``` + +### Stop containers + +```bash +docker compose down +``` + +Command to remove database data if needed: + +```bash +docker compose down -v +``` + +--- + +## Manual setup + +Use this when you prefer to run the project without Docker. + +### Database (PostgreSQL) + +We recommend using PostgreSQL as it is well-suited for production and fully supports ALTER, simplifying migrations. +However, you can use SQLite with the `sqlite+aiosqlite://` scheme. +For example: `sqlite+aiosqlite:///app.db` + +1. Install PostgreSQL 16 (or compatible) locally. +2. Create a database and user matching your connection string, for example: + +```sql +CREATE USER tournament WITH PASSWORD 'tournament'; +CREATE DATABASE tournament OWNER tournament; +``` + +3. Set `SQLALCHEMY_DATABASE_URI` in the root `.env` (see `.env.example`). Use the `postgresql+asyncpg://` scheme. + For example: `postgresql+asyncpg://tournament:tournament@localhost:5432/tournament` + +### Backend + +From the **repository root**: +First install Python 3.12 if you don't have it yet. + +```bash +python3.12 -m venv .venv +source .venv/bin/activate # Windows: .venv\Scripts\activate +pip install --upgrade pip +pip install -r requirements.txt +``` + +Ensure the root `.env` exists (copy from `.env.example`) with `SECRET_KEY`, `SQLALCHEMY_DATABASE_URI`, `FRONTEND_URL`, and all Firebase-related keys (same names as in `.env.example`). + +Place `serviceAccountKey.json` under `backend/app/`, without it authentication will not work. + +From the **`backend/`** directory: + +```bash +cd backend +alembic upgrade head +python -m app.init_db +python main.py +``` + +`python main.py` runs **Uvicorn** with reload on `http://127.0.0.1:8000` using the combined FastAPI + Socket.IO app (`app:socket_app`). + +Equivalent without reload: + +```bash +uvicorn app:socket_app --host 0.0.0.0 --port 8000 +``` + +### Frontend + +Requires **Node.js** (the Docker image uses Node 22; CI uses Node 20). + +```bash +cd frontend +npm install +npm run dev +``` + +Vite is configured with `envDir: ".."` so it loads `.env` from the **repository root**, not only from `frontend/`. + +--- + +## Environment variables + +Configuration is driven by a **`.env` file at the repository root** (see `backend/app/config.py`: `ENV_PATH` and `Settings`). + +| Variable | Required | Purpose | +| -------------------------- | --------------- | --------------------------------------------------------------------------------------------------------------- | +| `SECRET_KEY` | Yes | Signing key for sessions (e.g. SQLAdmin). | +| `SQLALCHEMY_DATABASE_URI` | Yes | Async SQLAlchemy URL (`postgresql+asyncpg://...` for Postgres). | +| `FRONTEND_URL` | Yes | Frontend origin (CORS-related usage, admin UI links). | +| `VITE_BACKEND_URL` | Yes (frontend) | Base URL for REST calls from the browser. | +| `VITE_SOCKETIO_SERVER_URL` | Yes (frontend) | Socket.IO server URL (same host/port as API in typical setups). | +| `VITE_FIREBASE_*` | Yes for auth UI | Firebase web app config; backend `Settings` reads the same names (or `FIREBASE_*` aliases where noted in code). | + +--- + +## Database setup + +- **Docker:** Postgres is defined in `docker-compose.yml` (`postgres:16-alpine`) with database/user/password `tournament`. +- **Migrations:** Managed with **Alembic** (`backend/alembic/`). Run `alembic upgrade head` from `backend/` after setting `SQLALCHEMY_DATABASE_URI`. +- **Seeding / static reference data:** On container start, the backend runs `python -m app.init_db` after migrations. That script creates tables if needed and runs `init_static_data` (roles, statuses, categories, etc.). The FastAPI **lifespan** hook also runs `init_static_data` on startup (`backend/app/__init__.py`). Source definitions are ultimately driven by `shared/app_config.json` and `backend/app/core/seeds/`. +- **Seed users:** There are **no default application passwords**; users authenticate with **Firebase**. Create users by signing in through the UI once Firebase is configured. + +--- + +## Running tests + +Commands below match `.github/workflows/tests.yml` and `package.json`. + +### Frontend (`frontend/`) + +```bash +cd frontend +npm install +npm run test +``` + +Coverage (Vitest + v8): + +```bash +npm run test:coverage +``` + +Interactive UI: + +```bash +npm run test:ui +``` + +### Backend (`backend/`) + +CI sets `SQLALCHEMY_DATABASE_URI` to SQLite and runs: + +```bash +cd backend +pip install -r ../requirements.txt +pytest +``` + +There is **no** dedicated `pytest` coverage script in `requirements.txt`; add tools such as `pytest-cov` locally if you need coverage reports. + +--- + +## Useful commands + +| Task | Command | +| ----------------------------------------------------------------------------------- | ----------------------------------------------- | +| Start the app in a container | `docker compose up --build` | +| Stop the app in a container | `docker compose down` | +| Reset database data in container | `docker compose down -v` | +| All these commands can be run in containers, following the `docker compose` pattern | +| Create a migration | `cd backend && alembic revision --autogenerate` | +| Backend migrations | `cd backend && alembic upgrade head` | +| Backend one-off seed script | `cd backend && python -m app.init_db` | +| Backend dev server | `cd backend && python main.py` | +| Frontend dev server | `cd frontend && npm run dev` | +| Frontend production build | `cd frontend && npm run build` | +| Frontend linter (Prettier) | `cd frontend && npm run lint` | +| Backend linter (Ruff) | `cd backend && ruff check .` | + +--- + +## Troubleshooting + +| Problem | What to try | +| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Docker build fails** | Ensure Docker has enough disk/RAM; run `docker compose build --no-cache`. On Linux, your user must be in the `docker` group or use `sudo` per your site policy. | +| **Backend container exits on start** | Check logs: `docker compose logs backend`. Confirm `SQLALCHEMY_DATABASE_URI` uses host `db` inside Compose (already set in `docker-compose.yml`). Ensure all required env vars exist (use `.env` from `.env.example`). | +| **Alembic / migration errors** | Run from `backend/` with the same `SQLALCHEMY_DATABASE_URI` as the running database. If branches were merged badly, resolve Alembic heads per Alembic docs. After schema mistakes, use `down -v` in development only if you can afford losing data. | +| **Database connection refused** | Postgres not ready: wait for healthcheck. Manual runs must use `localhost` and the correct port (default `5432`). | +| **`ModuleNotFoundError` (Python)** | Activate the venv using `python -m venv venv` and run `pip install -r requirements.txt` from the repo root. | +| **`npm` errors** | Use the Node version close to CI (20+) or Docker (22). Delete `node_modules` and run `npm ci`. | +| **Port already in use** | Change host ports in `docker-compose.yml` (e.g. `5174:5173`) or stop the process using `8000` / `5173` / `5432`. | +| **401 / Invalid id token** | Backend needs `backend/app/serviceAccountKey.json` from the **same** Firebase project as the frontend config. | +| **Blank or broken login UI** | Set all `VITE_FIREBASE_*` values in `.env` and rebuild/restart the frontend so Vite picks them up. | + +--- + +## Notes for reviewers + +- **No default accounts:** Access is via **Firebase Authentication** (e.g. Google). Configure a Firebase project, enable the sign-in providers you need, add web app credentials to `.env`, and add the Admin SDK JSON as `backend/app/serviceAccountKey.json`. +- **Admin SQLAdmin:** After a user exists and has the **admin** role in the database, they can sign in through the admin panel at `BACKEND_URL/admin` (Firebase-based; see `backend/app/admin/__init__.py`). +- **Demo-style data:** Some UI pieces import types or sample data from `frontend/src/data/mockTournaments.ts`; live tournament lists come from the API when authenticated. +- **Areas worth exercising:** Auth and profile sync, tournament CRUD (organizer flows), task and team flows, jury evaluation views, notifications (Socket.IO), role requests, news. +- **Known limitations:** Without Firebase and a service account file, you can still inspect static pages and API documentation, but **authenticated flows will not behave correctly**. Firebase Analytics is initialized in `frontend/src/firebase.ts` + +--- + +## Repository layout (reference) + +```text +docker-compose.yml # Orchestrates db, backend, frontend +requirements.txt # Python dependencies +backend/ # FastAPI app, Alembic, tests +frontend/ # Vite + React SPA +shared/ # Shared JSON config consumed by both tiers +.env.example # Template for root `.env` +``` + +If needed, we did not delete the Vite template README: `frontend/README.md`. + +## Quick evaluation flow + +1. Run the project with Docker +1. Open http://localhost:5173 +1. Sign in with Firebase +1. Grant yourself the organizer role, and also admin if you want access to the admin panel +1. Open the organizer panel +1. Create a tournament and optionally add someone to the jury committee +1. Add tasks +1. Register for your tournament +1. Now you can use the jury panel to evaluate tournament rounds. After evaluating all rounds, the tournament will conclude diff --git a/README.md b/README.md index f495082..ca969ca 100644 --- a/README.md +++ b/README.md @@ -1 +1,10 @@ -# Project \ No newline at end of file +This project was created as a tournament submission for the **Code4Future 2026** team programming competition organized by **Star for Life**. + +Цей проєкт було створено як турнірну роботу для командного турніру з програмування **Code4Future 2026**, організованого **Star for Life**. + + +## Choose language / Оберіть мову + +[![🇺🇦 Українська](https://img.shields.io/badge/🇺🇦_Мова-Українська-1565C0?style=for-the-badge)](./README.ua.md) + +[![🇬🇧 English](https://img.shields.io/badge/🇬🇧_Language-English-grey?style=for-the-badge)](./README.en.md) diff --git a/README.ua.md b/README.ua.md new file mode 100644 index 0000000..14cbc80 --- /dev/null +++ b/README.ua.md @@ -0,0 +1,325 @@ +

Документація українською мовою

+

+ + + +

+ +## Огляд проекту + +Це повнофункціональне застосування для **управління турнірами**: організатори можуть створювати і керувати турнірами, завданнями, командами та новинами; учасники проходять структурований процес; члени журі оцінюють роботи. Інтерфейс — це односторінкова React-програма, розташована на серверу **FastAPI** з **real-time сповіщеннями** через **Socket.IO**. + +**Основні можливості** + +- Автентифікація за допомогою пароля або Google OAuth(створено за допомогою Firebase Auth). +- Можливість переглядати турніри, реєструватися на них без попердньої реєстрації на платформі, подавати результати раундів. +- Організатори турнірів можуть маніпулювати турнірами, раундами, колегією журі та критеріями оцінювання. +- Журі, яких призначають організатори, мають зручну панель, де вони можуть оцінювати подачі користувачів. +- Користувачі можуть подати заявку на роль організатора, а адміністрація буде їх розглядати +- Наявна адмінь панель, створена за допомогою SQLAdmin. Тут можна повністю керувати базою даних +- Є сторінка новин на платформі, а також можливість надсилати глобальні сповіщення + +**Архітектура (коротко)** + +```text +Браузер (React + Vite) + │ HTTPS / REST + WebSocket (Socket.IO клієнт) + ▼ +FastAPI + python-socketio ASGI-застосування (`app:socket_app`) + │ SQLAlchemy (асинхронно) + Alembic міграції + ▼ +PostgreSQL +``` + +Загальна конфігурація для ролей, статусів та категорій розташована в `shared/app_config.json` і використовується як бекендом (`app.config`), так і фронтендом (`src/config/appConfig.ts`). +На основі цього файлу буде наповнюватися даними бд. Він дозволяє легко створювати, видаляти та редагувати статуси, категорії, ролі тощо. + +--- + +## Стек технологій + +| Область | Технології | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Фронтенд** | React 19, TypeScript, Vite 7, Tailwind CSS 4, Redux Toolkit, TanStack Query, Zod, React Router 7, Socket.IO client, Firebase JS SDK, Vitest, Testing Library | +| **Бекенд** | Python 3.12, FastAPI, Uvicorn, SQLAlchemy 2 (асинхронно), Alembic, Pydantic Settings, python-socketio, Firebase Admin SDK, SQLAdmin | +| **База даних** | PostgreSQL 16 (асинхронний драйвер: `asyncpg`; тести використовують SQLite через `aiosqlite`) | +| **Тестування** | Фронтенд: Vitest (`npm run test`, `npm run test:coverage`). Бекенд: pytest + pytest-asyncio (`pytest` з папки `backend/`, coverage: `pytest --cov=app tests`) | +| **Docker / розробка** | Docker Compose (`db`, `backend`, `frontend`), багатоступеневі Dockerfile'и в папках `backend/` та `frontend/` | + +--- + +## Запуск проєкту + +## Обов'язкові кроки + +### 1. Клонування репозиторію + +```bash +git clone +cd +``` + +### 2. Файл середовища + +```bash +cp .env.example .env +``` + +**Створення `.env`** + +```bash +cp .env.example .env +``` + +Відредагуйте `.env` і заповніть поля **Firebase** (див. [Змінні середовища](#змінні-середовища)). Стек може стартувати з порожніми рядками Firebase, але **вхід і захищені автентифікацією виклики API вимагають реальної конфігурації Firebase** та **ключа облікового запису служби** на бекенді (див. [Примітки для рецензентів](#примітки-для-рецензентів)). + +### 3. Firebase (обов'язково для функціонування автентефікації) + +Зареєструйтеся на платформі Firebase, створіть там проєкт та увімкніть авторизацію з використанням пароля та Google. Також потрібно створити Service account. Ось офіційні туторіали, що розповідають, як зробити це: +https://firebase.google.com/docs/admin/setup#set-up-project-and-service-account +https://firebase.google.com/docs/admin/setup#initialize_the_sdk_in_non-google_environments +Після того, як в вас буде .json файл з ключем, вам необхідно помістити його за шляхом нижче: + +`backend/app/serviceAccountKey.json` +\*Шлях може бути змінений в config.py в разі потреби + +Цей шлях занесено в .gitignore. Якщо ви змінили назву файлу, то рекомендуємо змінити і .gitignore. Завантажте JSON облікового запису служби з вашого проекту Firebase і збережіть його там. Якщо файл відсутній, бекенд все ще стартує, але **перевірка ID-токену**, яка використовується захищеними маршрутами, не запрацює, поки не буде присутній дійсний ключ. + +## Швидкий старт (рекомендується): Docker Compose + +Це найшвидший спосіб для розробників запустити **Postgres + API + Vite dev сервер** разом. + +### Передумови + +- [Docker](https://docs.docker.com/get-docker/) та Docker Compose v2 (`docker compose`). + +### Запуск усіх сервісів + +З **кореневої папки репозиторію** (де розташований `docker-compose.yml`): + +```bash +docker compose up --build -d +``` + +Дочекайтеся, поки перевірка здоров'я бази даних буде пройдена, а бекенд завершить `python -m app.init_db` перш ніж використовувати додаток. + +### Перебудова після змін залежностей + +```bash +docker compose build --no-cache +docker compose up -d +``` + +### Зупинка контейнерів + +```bash +docker compose down +``` + +Команда, щоб видалити дані БД в разі потреби: + +```bash +docker compose down -v +``` + +--- + +## Ручний запуск + +Використовуйте це, коли ви бажаєте запустити проєкт без використання Docker. + +### База даних (PostgreSQL) + +Рекомендуємо використовувати саме PostgreSQL, так як вона чудово підходить для продакшну, повністю підтримує ALTER, що спрощує міграції. +Однак ви можете використовувати sqlite. Для цього використовуйте схему `sqlite+aiosqlite://`. +Наприклад: `sqlite+aiosqlite:///app.db` + +1. Встановіть PostgreSQL 16 (або сумісну версію) локально. +2. Створіть базу даних і користувача, які відповідають вашому рядку з'єднання, наприклад: + +```sql +CREATE USER tournament WITH PASSWORD 'tournament'; +CREATE DATABASE tournament OWNER tournament; +``` + +3. Встановіть `SQLALCHEMY_DATABASE_URI` в корневому `.env` (див. `.env.example`). Використовуйте схему, якщо `postgresql+asyncpg://`. + Наприклад: `postgresql+asyncpg://tournament:tournament@localhost:5432/tournament` + +### Бекенд + +З **кореневої папки репозиторію**: +Попердньо встановіть Python 3.12, якщо він у вас досі не встановлений + +```bash +python3.12 -m venv .venv +source .venv/bin/activate # Windows: .venv\Scripts\activate +pip install --upgrade pip +pip install -r requirements.txt +``` + +Переконайтеся, що корневий `.env` існує (скопіюйте з `.env.example`) з `SECRET_KEY`, `SQLALCHEMY_DATABASE_URI`, `FRONTEND_URL` та усіма ключами, пов'язаними з Firebase (такими ж назвами, як у `.env.example`). + +Поставте `serviceAccountKey.json` в папку `backend/app/`, без нього не буде працювати автентифікація. + +З папки **`backend/`**: + +```bash +cd backend +alembic upgrade head +python -m app.init_db +python main.py +``` + +`python main.py` запускає **Uvicorn** з перезавантаженням на `http://127.0.0.1:8000` з використанням комбінованого застосування FastAPI + Socket.IO (`app:socket_app`). + +Еквівалент без перезавантаження: + +```bash +uvicorn app:socket_app --host 0.0.0.0 --port 8000 +``` + +### Фронтенд + +Вимагає **Node.js** (Docker-образ використовує Node 22; CI використовує Node 20). + +```bash +cd frontend +npm install +npm run dev +``` + +Vite налаштовано з `envDir: ".."`, тому він завантажує `.env` з **кореневої папки репозиторію**, не тільки з `frontend/`. + +--- + +## Змінні середовища + +Конфігурація керується **файлом `.env` в корені репозиторію** (див. `backend/app/config.py`: `ENV_PATH` та `Settings`). + +| Змінна | Обов'язково | Призначення | +| -------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `SECRET_KEY` | Так | Ключ для підпису сеансів (наприклад, SQLAdmin). | +| `SQLALCHEMY_DATABASE_URI` | Так | URL асинхронного SQLAlchemy (`postgresql+asyncpg://...` для Postgres). | +| `FRONTEND_URL` | Так | URL фронтенду (використання, пов'язане з CORS, посилання адміністративної панелі). | +| `VITE_BACKEND_URL` | Так (фронтенд) | Базовий URL для REST-викликів з браузера. | +| `VITE_SOCKETIO_SERVER_URL` | Так (фронтенд) | URL сервера Socket.IO (той же хост/порт, що й API, у типовому налаштуванні). | +| `VITE_FIREBASE_*` | Так для автентифікаційного інтерфейсу | Конфігурація веб-застосування Firebase; бекенд `Settings` читає такі ж назви (або альтернативи `FIREBASE_*`, де зазначено в коді). | + +--- + +## Налаштування бази даних + +- **Docker:** Postgres визначено в `docker-compose.yml` (`postgres:16-alpine`) з базою даних/користувачем/паролем `tournament`. +- **Міграції:** Керуються **Alembic** (`backend/alembic/`). Запустіть `alembic upgrade head` з папки `backend/` після налаштування `SQLALCHEMY_DATABASE_URI`. +- **Заповнення / статичні дані**: При запуску контейнера бекенд запускає `python -m app.init_db` після міграцій. Цей скрипт створює таблиці, якщо це необхідно, і запускає `init_static_data` (ролі, статуси, категорії тощо). Перехоплювач **lifespan** FastAPI також запускає `init_static_data` при запуску (`backend/app/__init__.py`). Визначення джерела керуються в кінцевому підсумку `shared/app_config.json` та `backend/app/core/seeds/`. +- **Заповнення користувачами:** **Немає стандартних паролів програми**; користувачі аутентифікуються через **Firebase**. Створюйте користувачів, зайшовши через інтерфейс після налаштування Firebase. + +--- + +## Запуск тестів + +Наведені нижче команди відповідають `.github/workflows/tests.yml` та `package.json`. + +### Фронтенд (`frontend/`) + +```bash +cd frontend +npm install +npm run test +``` + +Покриття (Vitest + v8): + +```bash +npm run test:coverage +``` + +Інтерактивний інтерфейс: + +```bash +npm run test:ui +``` + +### Бекенд (`backend/`) + +CI встановлює `SQLALCHEMY_DATABASE_URI` на SQLite і запускає: + +```bash +cd backend +pip install -r ../requirements.txt +pytest +``` + +**Немає** спеціального скрипту для покриття `pytest` в `requirements.txt`; додавайте інструменти на кшталт `pytest-cov` локально, якщо вам потрібні звіти про покриття. + +--- + +## Корисні команди + +| Завдання | Команда | +| -------------------------------------------------------------------------------------- | ----------------------------------------------- | +| Запустити додаток в контейнері | `docker compose up --build` | +| Зупинка додатку в контейнері | `docker compose down` | +| Скидання даних БД в контейнері | `docker compose down -v` | +| Всі ці команди можна запускати в контейнерах, треба писати по шаблону `docker compose` | +| Створити міграцію | `cd backend && alembic revision --autogenerate` | +| Міграції бекенду | `cd backend && alembic upgrade head` | +| Одноразовий seed скрипт бекенду | `cd backend && python -m app.init_db` | +| Девелопмент-сервер бекенду | `cd backend && python main.py` | +| Девелопмент-сервер фронтенду | `cd frontend && npm run dev` | +| Запустити білд фронтенду | `cd frontend && npm run build` | +| Лінтер фронтенду (Prettier) | `cd frontend && npm run lint` | +| Лінтер бекенду (Ruff) | `cd backend && ruff check .` | + +--- + +## Усунення неполадок + +| Проблема | Що спробувати | +| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Docker збірка не вдається** | Переконайтеся, що Docker має достатньо дискового простору/RAM; запустіть `docker compose build --no-cache`. На Linux ваш користувач повинен бути в групі `docker` або використовувати `sudo` відповідно до вашої політики сайту. | +| **Контейнер бекенду завершується при запуску** | Перевірте журнали: `docker compose logs backend`. Переконайтеся, що `SQLALCHEMY_DATABASE_URI` використовує хост `db` у Compose (вже встановлено в `docker-compose.yml`). Переконайтеся, що існують усі необхідні змінні середовища (використовуйте `.env` з `.env.example`). | +| **Помилки Alembic / міграції** | Запустіть з папки `backend/` з такою ж `SQLALCHEMY_DATABASE_URI`, як у поточної БД. Якщо гілки були об'єднані неправильно, розв'яжіть головки Alembic відповідно до документації Alembic. Після помилок схеми використовуйте `down -v` лише в розробці, якщо ви можете дозволити собі втратити дані. | +| **Відмовлено в'єднання до бази даних** | Postgres не готовий: дочекайтеся перевірки здоров'я. Ручні запуски повинні використовувати `localhost` та правильний порт (за замовчуванням `5432`). | +| **`ModuleNotFoundError` (Python)** | Активуйте venv за допомого `python -m venv venv` та запустіть `pip install -r requirements.txt` з кореня репозиторію. | +| **помилки `npm`** | Використовуйте версію Node близько до CI (20+) або Docker (22). Видаліть `node_modules` та запустіть `npm ci`. | +| **Порт уже використовується** | Змініть локальні порти в `docker-compose.yml` (наприклад, `5174:5173`) або зупиніть процес, який використовує `8000` / `5173` / `5432`. | +| **401 / Недійсний id token** | Бекенду потрібен `backend/app/serviceAccountKey.json` з **тією ж** Firebase проєкту, що й конфігурація фронтенду. | +| **Порожній або пошкоджений інтерфейс входу** | Встановіть усі значення `VITE_FIREBASE_*` в `.env` та перебудуйте/перезапустіть фронтенд, щоб Vite їх підібрав. | + +--- + +## Примітки для рецензентів + +- **Немає стандартних облікових записів:** Доступ здійснюється через **Firebase Authentication** (наприклад, Google). Налаштуйте проєкт Firebase, увімкніть потрібні вам постачальники входу, додайте облікові дані проєкту в `.env` та додайте JSON Admin SDK як `backend/app/serviceAccountKey.json`. +- **Адміністративна панель SQLAdmin:** Після того як користувач існує і має роль **admin** в БД, він може увійти в адмін-панель за адресою `BACKEND_URL/admin` (на основі Firebase; див. `backend/app/admin/__init__.py`). +- **Демо-подібні дані:** Деякі елементи інтерфейсу імпортують типи або приклади даних з `frontend/src/data/mockTournaments.ts`; списки живих турнірів надходять з API при аутентифікації. +- **Сфери для перевірки:** Аутентифікація та синхронізація профілів, CRUD операції турнірів (потоки організатора), потоки завдань та команд, подання журі, сповіщення (Socket.IO), запити ролей, новини. +- **Відомі обмеження:** Без Firebase та файлу облікового запису служби ви все ще можете переглянути статичні сторінки та документацію API, але **потоки аутентифікації не працюватимуть коректно**. Firebase Analytics ініціалізується в `frontend/src/firebase.ts` + +--- + +## Макет репозиторію (довідка) + +```text +docker-compose.yml # Містить контейнери db, backend, frontend +requirements.txt # Python залежності +backend/ # FastAPI застосування, Alembic, тести +frontend/ # Vite + React SPA +shared/ # Загальна JSON конфігурація, яка використовується бекендом та фронтендом +.env.example # Шаблон для кореневого `.env` +``` + +Якщо вам потрібно, то ми не видаляли README шаблону Vite: `frontend/README.md`. + +## Швидкий процес оцінювання + +1. Запустіть проект за допомогою Docker +1. Відкрийте http://localhost:5173 +1. Увійдіть за допомогою Firebase +1. Видайте собі роль організатора, а також адміна, якщо хочете мати доступ до адмін-панелі +1. Відкрийте панель організатора +1. Створіть турнір та опціонально додайте когось в комітет журі +1. Додайте завдання +1. Зареєструйтеся на ваш турнір +1. Тепер ви можете використати панель журі, щоб оцінити раунди турніру. Після оцінки всіх раундів турнір завершиться diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..cf489be --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,25 @@ +FROM python:3.12-slim + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +WORKDIR /app + +RUN apt-get update && apt-get install -y \ + gcc \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements.txt ./requirements.txt + +RUN pip install --no-cache-dir --upgrade pip \ + && pip install --no-cache-dir -r requirements.txt + +COPY backend ./backend +COPY shared ./shared + +WORKDIR /app/backend + +EXPOSE 8000 + +CMD ["sh", "-c", "alembic stamp head && python -m app.init_db && uvicorn app:socket_app --host 0.0.0.0 --port 8000"] diff --git a/backend/alembic.ini b/backend/alembic.ini new file mode 100644 index 0000000..42c1090 --- /dev/null +++ b/backend/alembic.ini @@ -0,0 +1,116 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = alembic + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python>=3.9 or backports.zoneinfo library. +# Any required deps can installed by adding `alembic[tz]` to the pip requirements +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to migrations/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the exec runner, execute a binary +# hooks = ruff +# ruff.type = exec +# ruff.executable = %(here)s/.venv/bin/ruff +# ruff.options = --fix REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/backend/alembic/README b/backend/alembic/README new file mode 100644 index 0000000..e0d0858 --- /dev/null +++ b/backend/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration with an async dbapi. \ No newline at end of file diff --git a/backend/alembic/env.py b/backend/alembic/env.py new file mode 100644 index 0000000..8b9212e --- /dev/null +++ b/backend/alembic/env.py @@ -0,0 +1,93 @@ +import asyncio +from logging.config import fileConfig + +from sqlalchemy import pool +from sqlalchemy.engine import Connection +from sqlalchemy.ext.asyncio import async_engine_from_config +from app.config import settings +from app.models import Base + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config +config.set_main_option('sqlalchemy.url', settings.SQLALCHEMY_DATABASE_URI) + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = Base.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + render_as_batch=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def do_run_migrations(connection: Connection) -> None: + context.configure(connection=connection, target_metadata=target_metadata, render_as_batch=True) + + with context.begin_transaction(): + context.run_migrations() + + +async def run_async_migrations() -> None: + """In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + connectable = async_engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + async with connectable.connect() as connection: + await connection.run_sync(do_run_migrations) + + await connectable.dispose() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode.""" + + asyncio.run(run_async_migrations()) + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/backend/alembic/script.py.mako b/backend/alembic/script.py.mako new file mode 100644 index 0000000..1101630 --- /dev/null +++ b/backend/alembic/script.py.mako @@ -0,0 +1,28 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + """Upgrade schema.""" + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + """Downgrade schema.""" + ${downgrades if downgrades else "pass"} diff --git a/backend/alembic/versions/127c3fae41db_.py b/backend/alembic/versions/127c3fae41db_.py new file mode 100644 index 0000000..9dc9c84 --- /dev/null +++ b/backend/alembic/versions/127c3fae41db_.py @@ -0,0 +1,276 @@ +"""empty message + +Revision ID: 127c3fae41db +Revises: 128f25c448b0 +Create Date: 2026-05-08 15:59:19.355990 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '127c3fae41db' +down_revision: Union[str, Sequence[str], None] = '128f25c448b0' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('news_categories', + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name') + ) + with op.batch_alter_table('news_categories', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_news_categories_name'), ['name'], unique=False) + + op.create_table('role_request_info_options', + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name') + ) + op.create_table('roles', + sa.Column('description', sa.String(), nullable=False), + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name') + ) + op.create_table('submission_url_options', + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name') + ) + op.create_table('task_requirement_categories', + sa.Column('main_id', sa.String(), nullable=True), + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['main_id'], ['task_requirement_categories.name'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('name') + ) + op.create_table('task_statuses', + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name') + ) + op.create_table('tournament_status_options', + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name') + ) + with op.batch_alter_table('tournament_status_options', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_tournament_status_options_name'), ['name'], unique=False) + + op.create_table('users', + sa.Column('firebase_uid', sa.String(), nullable=False), + sa.Column('full_name', sa.String(), nullable=False), + sa.Column('email', sa.String(), nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('telegram', sa.String(), nullable=True), + sa.Column('github', sa.String(), nullable=True), + sa.Column('discord', sa.String(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('email'), + sa.UniqueConstraint('firebase_uid') + ) + op.create_table('news', + sa.Column('title', sa.String(length=512), nullable=False), + sa.Column('excerpt', sa.String(length=512), nullable=False), + sa.Column('body', sa.String(), nullable=False), + sa.Column('is_important', sa.Boolean(), nullable=False), + sa.Column('category_name', sa.String(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.ForeignKeyConstraint(['category_name'], ['news_categories.name'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('notifications', + sa.Column('body', sa.String(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('role_requests', + sa.Column('role_name', sa.String(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['role_name'], ['roles.name'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('task_requirement_options', + sa.Column('category_id', sa.String(), nullable=False), + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['category_id'], ['task_requirement_categories.name'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('name') + ) + op.create_table('tasks', + sa.Column('title', sa.String(), nullable=False), + sa.Column('description', sa.String(), nullable=True), + sa.Column('start_time', sa.DateTime(), nullable=False), + sa.Column('end_time', sa.DateTime(), nullable=False), + sa.Column('tournament_id', sa.Integer(), nullable=False), + sa.Column('status_id', sa.String(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['status_id'], ['task_statuses.name'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['tournament_id'], ['tournaments.id'], name='fk_task_tournament', ondelete='CASCADE', use_alter=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('tournaments', + sa.Column('title', sa.String(), nullable=False), + sa.Column('description', sa.String(), nullable=False), + sa.Column('start_date', sa.DateTime(), nullable=False), + sa.Column('reg_start', sa.DateTime(), nullable=False), + sa.Column('reg_end', sa.DateTime(), nullable=False), + sa.Column('min_people_in_team', sa.Integer(), nullable=False), + sa.Column('max_people_in_team', sa.Integer(), nullable=False), + sa.Column('max_teams', sa.Integer(), nullable=False), + sa.Column('status_id', sa.String(), nullable=False), + sa.Column('creator_id', sa.Integer(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['creator_id'], ['users.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['status_id'], ['tournament_status_options.name'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('user_roles', + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('role_name', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['role_name'], ['roles.name'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('user_id', 'role_name') + ) + op.create_table('role_request_info', + sa.Column('request_id', sa.Integer(), nullable=False), + sa.Column('option_name', sa.String(), nullable=False), + sa.Column('value', sa.String(length=4096), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['option_name'], ['role_request_info_options.name'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['request_id'], ['role_requests.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('task_requirements', + sa.Column('task_id', sa.Integer(), nullable=False), + sa.Column('requirement_id', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['requirement_id'], ['task_requirement_options.name'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['task_id'], ['tasks.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('task_id', 'requirement_id') + ) + op.create_table('team_members', + sa.Column('full_name', sa.String(), nullable=False), + sa.Column('email', sa.String(), nullable=False), + sa.Column('telegram', sa.String(), nullable=False), + sa.Column('educational_institution', sa.String(), nullable=True), + sa.Column('team_id', sa.Integer(), nullable=False), + sa.Column('tournament_id', sa.Integer(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['team_id'], ['teams.id'], name='fk_teammember_team', ondelete='CASCADE', use_alter=True), + sa.ForeignKeyConstraint(['tournament_id'], ['tournaments.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('team_id', 'email', name='uq_team_member_email'), + sa.UniqueConstraint('team_id', 'telegram', name='uq_team_member_telegram'), + sa.UniqueConstraint('tournament_id', 'email', name='uq_tournament_member_email'), + sa.UniqueConstraint('tournament_id', 'telegram', name='uq_tournament_member_telegram') + ) + op.create_table('tournament_juries', + sa.Column('jury_id', sa.Integer(), nullable=False), + sa.Column('tournament_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['jury_id'], ['users.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['tournament_id'], ['tournaments.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('jury_id', 'tournament_id') + ) + op.create_table('teams', + sa.Column('name', sa.String(), nullable=False), + sa.Column('team_email', sa.String(), nullable=False), + sa.Column('contact_info', sa.String(), nullable=False), + sa.Column('tournament_id', sa.Integer(), nullable=False), + sa.Column('captain_id', sa.Integer(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['captain_id'], ['team_members.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['tournament_id'], ['tournaments.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('contact_info'), + sa.UniqueConstraint('contact_info', 'tournament_id'), + sa.UniqueConstraint('name'), + sa.UniqueConstraint('team_email') + ) + op.create_table('submissions', + sa.Column('team_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['team_id'], ['teams.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('team_id') + ) + op.create_table('evaluations', + sa.Column('submission_id', sa.Integer(), nullable=False), + sa.Column('jury_id', sa.Integer(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['jury_id'], ['users.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['submission_id'], ['submissions.team_id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('submission_id', 'jury_id') + ) + op.create_table('submission_urls', + sa.Column('submission_id', sa.Integer(), nullable=False), + sa.Column('url_id', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['submission_id'], ['submissions.team_id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['url_id'], ['submission_url_options.name'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('submission_id', 'url_id') + ) + op.create_table('requirement_evaluations', + sa.Column('evaluation_id', sa.Integer(), nullable=False), + sa.Column('score', sa.Integer(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['evaluation_id'], ['evaluations.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('evaluation_requirements', + sa.Column('requirement_evaluation_id', sa.Integer(), nullable=False), + sa.Column('requirement_id', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['requirement_evaluation_id'], ['requirement_evaluations.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['requirement_id'], ['task_requirement_options.name'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('requirement_evaluation_id', 'requirement_id') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('evaluation_requirements') + op.drop_table('requirement_evaluations') + op.drop_table('submission_urls') + op.drop_table('evaluations') + op.drop_table('submissions') + op.drop_table('teams') + op.drop_table('tournament_juries') + op.drop_table('team_members') + op.drop_table('task_requirements') + op.drop_table('role_request_info') + op.drop_table('user_roles') + op.drop_table('tournaments') + op.drop_table('tasks') + op.drop_table('task_requirement_options') + op.drop_table('role_requests') + op.drop_table('notifications') + op.drop_table('news') + op.drop_table('users') + with op.batch_alter_table('tournament_status_options', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_tournament_status_options_name')) + + op.drop_table('tournament_status_options') + op.drop_table('task_statuses') + op.drop_table('task_requirement_categories') + op.drop_table('submission_url_options') + op.drop_table('roles') + op.drop_table('role_request_info_options') + with op.batch_alter_table('news_categories', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_news_categories_name')) + + op.drop_table('news_categories') + # ### end Alembic commands ### diff --git a/backend/alembic/versions/128f25c448b0_.py b/backend/alembic/versions/128f25c448b0_.py new file mode 100644 index 0000000..f66b3a4 --- /dev/null +++ b/backend/alembic/versions/128f25c448b0_.py @@ -0,0 +1,276 @@ +"""empty message + +Revision ID: 128f25c448b0 +Revises: dfc9ca7d5a57 +Create Date: 2026-05-07 21:42:31.511438 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '128f25c448b0' +down_revision: Union[str, Sequence[str], None] = 'dfc9ca7d5a57' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('news_categories', + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name') + ) + with op.batch_alter_table('news_categories', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_news_categories_name'), ['name'], unique=False) + + op.create_table('role_request_info_options', + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name') + ) + op.create_table('roles', + sa.Column('description', sa.String(), nullable=False), + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name') + ) + op.create_table('submission_url_options', + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name') + ) + op.create_table('task_requirement_categories', + sa.Column('main_id', sa.String(), nullable=True), + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['main_id'], ['task_requirement_categories.name'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('name') + ) + op.create_table('task_statuses', + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name') + ) + op.create_table('tournament_status_options', + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name') + ) + with op.batch_alter_table('tournament_status_options', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_tournament_status_options_name'), ['name'], unique=False) + + op.create_table('users', + sa.Column('firebase_uid', sa.String(), nullable=False), + sa.Column('full_name', sa.String(), nullable=False), + sa.Column('email', sa.String(), nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('telegram', sa.String(), nullable=True), + sa.Column('github', sa.String(), nullable=True), + sa.Column('discord', sa.String(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('email'), + sa.UniqueConstraint('firebase_uid') + ) + op.create_table('news', + sa.Column('title', sa.String(length=512), nullable=False), + sa.Column('excerpt', sa.String(length=512), nullable=False), + sa.Column('body', sa.String(), nullable=False), + sa.Column('is_important', sa.Boolean(), nullable=False), + sa.Column('category_name', sa.String(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.ForeignKeyConstraint(['category_name'], ['news_categories.name'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('notifications', + sa.Column('body', sa.String(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('role_requests', + sa.Column('role_name', sa.String(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['role_name'], ['roles.name'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('task_requirement_options', + sa.Column('category_id', sa.String(), nullable=False), + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['category_id'], ['task_requirement_categories.name'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('name') + ) + op.create_table('tasks', + sa.Column('title', sa.String(), nullable=False), + sa.Column('description', sa.String(), nullable=True), + sa.Column('start_time', sa.DateTime(), nullable=False), + sa.Column('end_time', sa.DateTime(), nullable=False), + sa.Column('tournament_id', sa.Integer(), nullable=False), + sa.Column('status_id', sa.String(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['status_id'], ['task_statuses.name'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['tournament_id'], ['tournaments.id'], name='fk_task_tournament', ondelete='CASCADE', use_alter=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('tournaments', + sa.Column('title', sa.String(), nullable=False), + sa.Column('description', sa.String(), nullable=False), + sa.Column('start_date', sa.DateTime(), nullable=False), + sa.Column('reg_start', sa.DateTime(), nullable=False), + sa.Column('reg_end', sa.DateTime(), nullable=False), + sa.Column('min_people_in_team', sa.Integer(), nullable=False), + sa.Column('max_people_in_team', sa.Integer(), nullable=False), + sa.Column('max_teams', sa.Integer(), nullable=False), + sa.Column('status_id', sa.String(), nullable=False), + sa.Column('creator_id', sa.Integer(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['creator_id'], ['users.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['status_id'], ['tournament_status_options.name'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('user_roles', + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('role_name', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['role_name'], ['roles.name'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('user_id', 'role_name') + ) + op.create_table('role_request_info', + sa.Column('request_id', sa.Integer(), nullable=False), + sa.Column('option_name', sa.String(), nullable=False), + sa.Column('value', sa.String(length=4096), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['option_name'], ['role_request_info_options.name'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['request_id'], ['role_requests.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('task_requirements', + sa.Column('task_id', sa.Integer(), nullable=False), + sa.Column('requirement_id', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['requirement_id'], ['task_requirement_options.name'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['task_id'], ['tasks.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('task_id', 'requirement_id') + ) + op.create_table('team_members', + sa.Column('full_name', sa.String(), nullable=False), + sa.Column('email', sa.String(), nullable=False), + sa.Column('telegram', sa.String(), nullable=False), + sa.Column('educational_institution', sa.String(), nullable=True), + sa.Column('team_id', sa.Integer(), nullable=False), + sa.Column('tournament_id', sa.Integer(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['team_id'], ['teams.id'], name='fk_teammember_team', ondelete='CASCADE', use_alter=True), + sa.ForeignKeyConstraint(['tournament_id'], ['tournaments.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('team_id', 'email', name='uq_team_member_email'), + sa.UniqueConstraint('team_id', 'telegram', name='uq_team_member_telegram'), + sa.UniqueConstraint('tournament_id', 'email', name='uq_tournament_member_email'), + sa.UniqueConstraint('tournament_id', 'telegram', name='uq_tournament_member_telegram') + ) + op.create_table('tournament_juries', + sa.Column('jury_id', sa.Integer(), nullable=False), + sa.Column('tournament_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['jury_id'], ['users.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['tournament_id'], ['tournaments.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('jury_id', 'tournament_id') + ) + op.create_table('teams', + sa.Column('name', sa.String(), nullable=False), + sa.Column('team_email', sa.String(), nullable=False), + sa.Column('contact_info', sa.String(), nullable=False), + sa.Column('tournament_id', sa.Integer(), nullable=False), + sa.Column('captain_id', sa.Integer(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['captain_id'], ['team_members.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['tournament_id'], ['tournaments.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('contact_info'), + sa.UniqueConstraint('contact_info', 'tournament_id'), + sa.UniqueConstraint('name'), + sa.UniqueConstraint('team_email') + ) + op.create_table('submissions', + sa.Column('team_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['team_id'], ['teams.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('team_id') + ) + op.create_table('evaluations', + sa.Column('submission_id', sa.Integer(), nullable=False), + sa.Column('jury_id', sa.Integer(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['jury_id'], ['users.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['submission_id'], ['submissions.team_id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('submission_id', 'jury_id') + ) + op.create_table('submission_urls', + sa.Column('submission_id', sa.Integer(), nullable=False), + sa.Column('url_id', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['submission_id'], ['submissions.team_id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['url_id'], ['submission_url_options.name'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('submission_id', 'url_id') + ) + op.create_table('requirement_evaluations', + sa.Column('evaluation_id', sa.Integer(), nullable=False), + sa.Column('score', sa.Integer(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['evaluation_id'], ['evaluations.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('evaluation_requirements', + sa.Column('requirement_evaluation_id', sa.Integer(), nullable=False), + sa.Column('requirement_id', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['requirement_evaluation_id'], ['requirement_evaluations.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['requirement_id'], ['task_requirement_options.name'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('requirement_evaluation_id', 'requirement_id') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('evaluation_requirements') + op.drop_table('requirement_evaluations') + op.drop_table('submission_urls') + op.drop_table('evaluations') + op.drop_table('submissions') + op.drop_table('teams') + op.drop_table('tournament_juries') + op.drop_table('team_members') + op.drop_table('task_requirements') + op.drop_table('role_request_info') + op.drop_table('user_roles') + op.drop_table('tournaments') + op.drop_table('tasks') + op.drop_table('task_requirement_options') + op.drop_table('role_requests') + op.drop_table('notifications') + op.drop_table('news') + op.drop_table('users') + with op.batch_alter_table('tournament_status_options', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_tournament_status_options_name')) + + op.drop_table('tournament_status_options') + op.drop_table('task_statuses') + op.drop_table('task_requirement_categories') + op.drop_table('submission_url_options') + op.drop_table('roles') + op.drop_table('role_request_info_options') + with op.batch_alter_table('news_categories', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_news_categories_name')) + + op.drop_table('news_categories') + # ### end Alembic commands ### diff --git a/backend/alembic/versions/bd3c7aaffa33_init.py b/backend/alembic/versions/bd3c7aaffa33_init.py new file mode 100644 index 0000000..ec2bc11 --- /dev/null +++ b/backend/alembic/versions/bd3c7aaffa33_init.py @@ -0,0 +1,335 @@ +"""init + +Revision ID: bd3c7aaffa33 +Revises: +Create Date: 2026-05-13 20:21:46.540959 +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = "bd3c7aaffa33" +down_revision: Union[str, Sequence[str], None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + + op.create_table( + "jury_assignment_statuses", + sa.Column("name", sa.String(), nullable=False), + sa.Column("display_name", sa.String(), nullable=False), + sa.PrimaryKeyConstraint("name"), + ) + + op.create_table( + "task_evaluation_criteria", + sa.Column("task_id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(), nullable=False), + sa.Column("description", sa.String(), nullable=True), + sa.Column("weight", sa.Integer(), nullable=False), + sa.Column("max_score", sa.Integer(), nullable=False), + sa.Column("id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["task_id"], + ["tasks.id"], + ondelete="CASCADE", + ), + sa.PrimaryKeyConstraint("id"), + ) + + op.create_table( + "jury_assignments", + sa.Column("task_id", sa.Integer(), nullable=False), + sa.Column("submission_id", sa.Integer(), nullable=False), + sa.Column("jury_id", sa.Integer(), nullable=False), + sa.Column("status_id", sa.String(), nullable=False), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column( + "created_at", + sa.DateTime(), + server_default=sa.text("now()"), + nullable=False, + ), + sa.Column( + "updated_at", + sa.DateTime(), + server_default=sa.text("now()"), + nullable=False, + ), + sa.ForeignKeyConstraint( + ["jury_id"], + ["users.id"], + ondelete="CASCADE", + ), + sa.ForeignKeyConstraint( + ["status_id"], + ["jury_assignment_statuses.name"], + ondelete="CASCADE", + ), + sa.ForeignKeyConstraint( + ["submission_id"], + ["submissions.team_id"], + ondelete="CASCADE", + ), + sa.ForeignKeyConstraint( + ["task_id"], + ["tasks.id"], + ondelete="CASCADE", + ), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("submission_id", "jury_id"), + ) + + op.create_table( + "criterion_scores", + sa.Column("evaluation_id", sa.Integer(), nullable=False), + sa.Column("criterion_id", sa.Integer(), nullable=False), + sa.Column("score", sa.Integer(), nullable=False), + sa.Column("id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["criterion_id"], + ["task_evaluation_criteria.id"], + ondelete="CASCADE", + ), + sa.ForeignKeyConstraint( + ["evaluation_id"], + ["evaluations.id"], + ondelete="CASCADE", + ), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("evaluation_id", "criterion_id"), + ) + + op.drop_table("evaluation_requirements") + op.drop_table("requirement_evaluations") + + with op.batch_alter_table("evaluations", schema=None) as batch_op: + batch_op.add_column( + sa.Column( + "assignment_id", + sa.Integer(), + nullable=True, + ) + ) + + batch_op.add_column( + sa.Column( + "comment", + sa.String(), + nullable=True, + ) + ) + + batch_op.add_column( + sa.Column( + "created_at", + sa.DateTime(), + server_default=sa.text("now()"), + nullable=False, + ) + ) + + batch_op.add_column( + sa.Column( + "updated_at", + sa.DateTime(), + server_default=sa.text("now()"), + nullable=False, + ) + ) + + batch_op.create_foreign_key( + None, + "jury_assignments", + ["assignment_id"], + ["id"], + ondelete="CASCADE", + ) + + with op.batch_alter_table("submission_urls", schema=None) as batch_op: + batch_op.add_column( + sa.Column( + "value", + sa.String(), + nullable=True, + ) + ) + + with op.batch_alter_table("submissions", schema=None) as batch_op: + batch_op.add_column( + sa.Column( + "task_id", + sa.Integer(), + nullable=True, + ) + ) + + batch_op.create_foreign_key( + None, + "tasks", + ["task_id"], + ["id"], + ondelete="CASCADE", + ) + + with op.batch_alter_table("tasks", schema=None) as batch_op: + batch_op.add_column( + sa.Column( + "min_reviews_per_submission", + sa.Integer(), + nullable=False, + server_default="1", + ) + ) + + batch_op.add_column( + sa.Column( + "max_score", + sa.Integer(), + nullable=False, + server_default="100", + ) + ) + + batch_op.add_column( + sa.Column( + "is_leaderboard_visible", + sa.Boolean(), + nullable=False, + server_default=sa.text("false"), + ) + ) + + batch_op.create_foreign_key( + "fk_task_tournament", + "tournaments", + ["tournament_id"], + ["id"], + ondelete="CASCADE", + use_alter=True, + ) + + with op.batch_alter_table("team_members", schema=None) as batch_op: + batch_op.create_foreign_key( + "fk_teammember_team", + "teams", + ["team_id"], + ["id"], + ondelete="CASCADE", + use_alter=True, + ) + + +def downgrade() -> None: + """Downgrade schema.""" + + with op.batch_alter_table("team_members", schema=None) as batch_op: + batch_op.drop_constraint( + "fk_teammember_team", + type_="foreignkey", + ) + + with op.batch_alter_table("tasks", schema=None) as batch_op: + batch_op.drop_constraint( + "fk_task_tournament", + type_="foreignkey", + ) + + batch_op.drop_column("is_leaderboard_visible") + batch_op.drop_column("max_score") + batch_op.drop_column("min_reviews_per_submission") + + with op.batch_alter_table("submissions", schema=None) as batch_op: + batch_op.drop_constraint(None, type_="foreignkey") + batch_op.drop_column("task_id") + + with op.batch_alter_table("submission_urls", schema=None) as batch_op: + batch_op.drop_column("value") + + with op.batch_alter_table("evaluations", schema=None) as batch_op: + batch_op.drop_constraint(None, type_="foreignkey") + batch_op.drop_column("updated_at") + batch_op.drop_column("created_at") + batch_op.drop_column("comment") + batch_op.drop_column("assignment_id") + + op.create_table( + "requirement_evaluations", + sa.Column( + "evaluation_id", + sa.INTEGER(), + autoincrement=False, + nullable=False, + ), + sa.Column( + "score", + sa.INTEGER(), + autoincrement=False, + nullable=False, + ), + sa.Column( + "id", + sa.INTEGER(), + autoincrement=True, + nullable=False, + ), + sa.ForeignKeyConstraint( + ["evaluation_id"], + ["evaluations.id"], + name=op.f("requirement_evaluations_evaluation_id_fkey"), + ondelete="CASCADE", + ), + sa.PrimaryKeyConstraint( + "id", + name=op.f("requirement_evaluations_pkey"), + ), + ) + + op.create_table( + "evaluation_requirements", + sa.Column( + "requirement_evaluation_id", + sa.INTEGER(), + autoincrement=False, + nullable=False, + ), + sa.Column( + "requirement_id", + sa.VARCHAR(), + autoincrement=False, + nullable=False, + ), + sa.ForeignKeyConstraint( + ["requirement_evaluation_id"], + ["requirement_evaluations.id"], + name=op.f( + "evaluation_requirements_requirement_evaluation_id_fkey" + ), + ondelete="CASCADE", + ), + sa.ForeignKeyConstraint( + ["requirement_id"], + ["task_requirement_options.name"], + name=op.f( + "evaluation_requirements_requirement_id_fkey" + ), + ondelete="CASCADE", + ), + sa.PrimaryKeyConstraint( + "requirement_evaluation_id", + "requirement_id", + name=op.f("evaluation_requirements_pkey"), + ), + ) + + op.drop_table("criterion_scores") + op.drop_table("jury_assignments") + op.drop_table("task_evaluation_criteria") + op.drop_table("jury_assignment_statuses") \ No newline at end of file diff --git a/backend/alembic/versions/cfe129b6e3a3_.py b/backend/alembic/versions/cfe129b6e3a3_.py new file mode 100644 index 0000000..b347943 --- /dev/null +++ b/backend/alembic/versions/cfe129b6e3a3_.py @@ -0,0 +1,276 @@ +"""empty message + +Revision ID: cfe129b6e3a3 +Revises: 127c3fae41db +Create Date: 2026-05-11 14:37:01.268953 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'cfe129b6e3a3' +down_revision: Union[str, Sequence[str], None] = '127c3fae41db' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('news_categories', + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name') + ) + with op.batch_alter_table('news_categories', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_news_categories_name'), ['name'], unique=False) + + op.create_table('role_request_info_options', + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name') + ) + op.create_table('roles', + sa.Column('description', sa.String(), nullable=False), + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name') + ) + op.create_table('submission_url_options', + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name') + ) + op.create_table('task_requirement_categories', + sa.Column('main_id', sa.String(), nullable=True), + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['main_id'], ['task_requirement_categories.name'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('name') + ) + op.create_table('task_statuses', + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name') + ) + op.create_table('tournament_status_options', + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name') + ) + with op.batch_alter_table('tournament_status_options', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_tournament_status_options_name'), ['name'], unique=False) + + op.create_table('users', + sa.Column('firebase_uid', sa.String(), nullable=False), + sa.Column('full_name', sa.String(), nullable=False), + sa.Column('email', sa.String(), nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('telegram', sa.String(), nullable=True), + sa.Column('github', sa.String(), nullable=True), + sa.Column('discord', sa.String(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('email'), + sa.UniqueConstraint('firebase_uid') + ) + op.create_table('news', + sa.Column('title', sa.String(length=512), nullable=False), + sa.Column('excerpt', sa.String(length=512), nullable=False), + sa.Column('body', sa.String(), nullable=False), + sa.Column('is_important', sa.Boolean(), nullable=False), + sa.Column('category_name', sa.String(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.ForeignKeyConstraint(['category_name'], ['news_categories.name'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('notifications', + sa.Column('body', sa.String(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('role_requests', + sa.Column('role_name', sa.String(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['role_name'], ['roles.name'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('task_requirement_options', + sa.Column('category_id', sa.String(), nullable=False), + sa.Column('name', sa.String(), nullable=False), + sa.Column('display_name', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['category_id'], ['task_requirement_categories.name'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('name') + ) + op.create_table('tasks', + sa.Column('title', sa.String(), nullable=False), + sa.Column('description', sa.String(), nullable=True), + sa.Column('start_time', sa.DateTime(), nullable=False), + sa.Column('end_time', sa.DateTime(), nullable=False), + sa.Column('tournament_id', sa.Integer(), nullable=False), + sa.Column('status_id', sa.String(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['status_id'], ['task_statuses.name'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['tournament_id'], ['tournaments.id'], name='fk_task_tournament', ondelete='CASCADE', use_alter=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('tournaments', + sa.Column('title', sa.String(), nullable=False), + sa.Column('description', sa.String(), nullable=False), + sa.Column('start_date', sa.DateTime(), nullable=False), + sa.Column('reg_start', sa.DateTime(), nullable=False), + sa.Column('reg_end', sa.DateTime(), nullable=False), + sa.Column('min_people_in_team', sa.Integer(), nullable=False), + sa.Column('max_people_in_team', sa.Integer(), nullable=False), + sa.Column('max_teams', sa.Integer(), nullable=False), + sa.Column('status_id', sa.String(), nullable=False), + sa.Column('creator_id', sa.Integer(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['creator_id'], ['users.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['status_id'], ['tournament_status_options.name'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('user_roles', + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('role_name', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['role_name'], ['roles.name'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('user_id', 'role_name') + ) + op.create_table('role_request_info', + sa.Column('request_id', sa.Integer(), nullable=False), + sa.Column('option_name', sa.String(), nullable=False), + sa.Column('value', sa.String(length=4096), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['option_name'], ['role_request_info_options.name'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['request_id'], ['role_requests.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('task_requirements', + sa.Column('task_id', sa.Integer(), nullable=False), + sa.Column('requirement_id', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['requirement_id'], ['task_requirement_options.name'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['task_id'], ['tasks.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('task_id', 'requirement_id') + ) + op.create_table('team_members', + sa.Column('full_name', sa.String(), nullable=False), + sa.Column('email', sa.String(), nullable=False), + sa.Column('telegram', sa.String(), nullable=False), + sa.Column('educational_institution', sa.String(), nullable=True), + sa.Column('team_id', sa.Integer(), nullable=False), + sa.Column('tournament_id', sa.Integer(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['team_id'], ['teams.id'], name='fk_teammember_team', ondelete='CASCADE', use_alter=True), + sa.ForeignKeyConstraint(['tournament_id'], ['tournaments.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('team_id', 'email', name='uq_team_member_email'), + sa.UniqueConstraint('team_id', 'telegram', name='uq_team_member_telegram'), + sa.UniqueConstraint('tournament_id', 'email', name='uq_tournament_member_email'), + sa.UniqueConstraint('tournament_id', 'telegram', name='uq_tournament_member_telegram') + ) + op.create_table('tournament_juries', + sa.Column('jury_id', sa.Integer(), nullable=False), + sa.Column('tournament_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['jury_id'], ['users.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['tournament_id'], ['tournaments.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('jury_id', 'tournament_id') + ) + op.create_table('teams', + sa.Column('name', sa.String(), nullable=False), + sa.Column('team_email', sa.String(), nullable=False), + sa.Column('contact_info', sa.String(), nullable=False), + sa.Column('tournament_id', sa.Integer(), nullable=False), + sa.Column('captain_id', sa.Integer(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['captain_id'], ['team_members.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['tournament_id'], ['tournaments.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('contact_info'), + sa.UniqueConstraint('contact_info', 'tournament_id'), + sa.UniqueConstraint('name'), + sa.UniqueConstraint('team_email') + ) + op.create_table('submissions', + sa.Column('team_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['team_id'], ['teams.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('team_id') + ) + op.create_table('evaluations', + sa.Column('submission_id', sa.Integer(), nullable=False), + sa.Column('jury_id', sa.Integer(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['jury_id'], ['users.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['submission_id'], ['submissions.team_id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('submission_id', 'jury_id') + ) + op.create_table('submission_urls', + sa.Column('submission_id', sa.Integer(), nullable=False), + sa.Column('url_id', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['submission_id'], ['submissions.team_id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['url_id'], ['submission_url_options.name'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('submission_id', 'url_id') + ) + op.create_table('requirement_evaluations', + sa.Column('evaluation_id', sa.Integer(), nullable=False), + sa.Column('score', sa.Integer(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['evaluation_id'], ['evaluations.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('evaluation_requirements', + sa.Column('requirement_evaluation_id', sa.Integer(), nullable=False), + sa.Column('requirement_id', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['requirement_evaluation_id'], ['requirement_evaluations.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['requirement_id'], ['task_requirement_options.name'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('requirement_evaluation_id', 'requirement_id') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('evaluation_requirements') + op.drop_table('requirement_evaluations') + op.drop_table('submission_urls') + op.drop_table('evaluations') + op.drop_table('submissions') + op.drop_table('teams') + op.drop_table('tournament_juries') + op.drop_table('team_members') + op.drop_table('task_requirements') + op.drop_table('role_request_info') + op.drop_table('user_roles') + op.drop_table('tournaments') + op.drop_table('tasks') + op.drop_table('task_requirement_options') + op.drop_table('role_requests') + op.drop_table('notifications') + op.drop_table('news') + op.drop_table('users') + with op.batch_alter_table('tournament_status_options', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_tournament_status_options_name')) + + op.drop_table('tournament_status_options') + op.drop_table('task_statuses') + op.drop_table('task_requirement_categories') + op.drop_table('submission_url_options') + op.drop_table('roles') + op.drop_table('role_request_info_options') + with op.batch_alter_table('news_categories', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_news_categories_name')) + + op.drop_table('news_categories') + # ### end Alembic commands ### diff --git a/backend/alembic/versions/d146519cd8df_remove_is_global_column_from_.py b/backend/alembic/versions/d146519cd8df_remove_is_global_column_from_.py new file mode 100644 index 0000000..8c7d57a --- /dev/null +++ b/backend/alembic/versions/d146519cd8df_remove_is_global_column_from_.py @@ -0,0 +1,28 @@ +"""Remove is_global column from notifications table + +Revision ID: d146519cd8df +Revises: 6150229200ba +Create Date: 2026-05-03 20:18:01.936401 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'd146519cd8df' +down_revision: Union[str, Sequence[str], None] = '6150229200ba' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + pass + + +def downgrade() -> None: + """Downgrade schema.""" + pass diff --git a/backend/app/__init__.py b/backend/app/__init__.py new file mode 100644 index 0000000..271253d --- /dev/null +++ b/backend/app/__init__.py @@ -0,0 +1,57 @@ +import socketio +from contextlib import asynccontextmanager +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +import app.routes.jury as jury +import app.routes.news as news +import app.routes.profile as profile +import app.routes.role_requests as role_requests +import app.routes.roles as roles +import app.routes.tasks as tasks +import app.routes.team_members as team_members +import app.routes.teams as teams +import app.routes.tournaments as tournaments +import app.routes.users as users + +from app.core.seeds import init_static_data +from app.db import AsyncSessionLocal +from .config import settings +from .admin import setup_admin + + +# temporary decision +@asynccontextmanager +async def lifespan(app: FastAPI): + async with AsyncSessionLocal() as session: + await init_static_data(session) + yield + + +app = FastAPI(lifespan=lifespan) +app.add_middleware( + CORSMiddleware, + allow_origins=settings.CORS_ORIGINS, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) +app.state.user_websocket_sessions = {} + + +app.include_router(jury.router) +app.include_router(news.router) +app.include_router(profile.router) +app.include_router(role_requests.router) +app.include_router(roles.router) +app.include_router(tasks.router) +app.include_router(team_members.router) +app.include_router(teams.router) +app.include_router(tournaments.router) +app.include_router(users.router) + +from .websockets import * + +socket_app = socketio.ASGIApp(sio, other_asgi_app=app) + +setup_admin(app) diff --git a/backend/app/admin/__init__.py b/backend/app/admin/__init__.py new file mode 100644 index 0000000..04278e8 --- /dev/null +++ b/backend/app/admin/__init__.py @@ -0,0 +1,343 @@ +from sqladmin import Admin, ModelView, action, Flash +from sqladmin import _menu +from sqladmin.authentication import AuthenticationBackend +from fastapi import Request +from fastapi.responses import RedirectResponse +from firebase_admin import auth +from sqlalchemy import select +from sqlalchemy.orm import selectinload + +from app.db import engine, AsyncSessionLocal +from app.utils import reject_role_request, approve_role_request +from app.models import ( + JuryAssignment, + Notification, + CriterionScore, + Role, + RoleRequest, + Submission, + SubmissionEvaluation, + SubmissionUrlOption, + SubmissionUrl, + Task, + TaskEvaluationCriterion, + TaskRequirementCategory, + TaskRequirementOption, + TaskStatusOption, + Team, + TeamMember, + Tournament, + TournamentStatusOption, + User, + News, + NewsCattegory, +) +from app.config import settings +from app.firebase import firebase +from app.dependencies import get_or_create_user_from_token + + +class BaseModelView(ModelView): + form_excluded_columns = ("created_at", "updated_at") + + +class NamePrimaryKeyAdmin(BaseModelView): + form_include_pk = True + + +class UserAdmin(BaseModelView, model=User): + column_list = [ + User.id, + User.full_name, + User.email, + User.firebase_uid, + User.created_at, + ] + column_searchable_list = [User.full_name, User.email, User.firebase_uid] + + +class RoleAdmin(NamePrimaryKeyAdmin, model=Role): + column_list = [Role.name, Role.display_name, Role.description] + column_searchable_list = [Role.name] + + +class RoleRequestAdmin(BaseModelView, model=RoleRequest): + column_list = [RoleRequest.id, RoleRequest.user_id, RoleRequest.role_name] + list_template = "role_request_list.html" + + async def _get_role_request(self, session, pk: str) -> RoleRequest | None: + stmt = ( + select(RoleRequest) + .options( + selectinload(RoleRequest.user).selectinload(User.roles), + selectinload(RoleRequest.role), + ) + .where(RoleRequest.id == int(pk)) + ) + return (await session.execute(stmt)).scalar_one_or_none() + + @action( + name="reject_request", + label="Reject", + confirmation_message="Are you sure?", + add_in_detail=True, + add_in_list=True, + include_in_schema=True, + ) + async def reject_request(self, request: Request): + pks = request.query_params.get("pks", "").split(",") + async with self.session_maker() as session: + for pk in filter(None, pks): + req = await self._get_role_request(session, pk) + if req is not None: + await reject_role_request(req, session) + + referer = request.headers.get("Referer") + Flash.success(request, "Role request rejected successfully") + if referer: + return RedirectResponse(referer) + else: + return RedirectResponse(request.url_for("admin:list", identity=self.identity)) + + @action( + name="approve_request", + label="Approve", + confirmation_message="Are you sure?", + add_in_detail=True, + add_in_list=True, + include_in_schema=True, + ) + async def approve_request(self, request: Request): + pks = request.query_params.get("pks", "").split(",") + async with self.session_maker() as session: + for pk in filter(None, pks): + req = await self._get_role_request(session, pk) + if req is not None: + await approve_role_request(req, session) + + referer = request.headers.get("Referer") + Flash.success(request, "Role request approved successfully") + if referer: + return RedirectResponse(referer) + else: + return RedirectResponse(request.url_for("admin:list", identity=self.identity)) + + +class TournamentAdmin(BaseModelView, model=Tournament): + column_list = [ + Tournament.id, + Tournament.title, + Tournament.start_date, + Tournament.reg_start, + Tournament.reg_end, + Tournament.max_teams, + Tournament.status_id, + Tournament.creator_id, + ] + column_searchable_list = [Tournament.title] + + +class TournamentStatusOptionAdmin(NamePrimaryKeyAdmin, model=TournamentStatusOption): + column_list = [TournamentStatusOption.name, TournamentStatusOption.display_name] + + +class TaskAdmin(BaseModelView, model=Task): + column_list = [ + Task.id, + Task.title, + Task.tournament_id, + Task.start_time, + Task.end_time, + Task.status_id, + ] + column_searchable_list = [Task.title] + + +class TaskStatusOptionAdmin(NamePrimaryKeyAdmin, model=TaskStatusOption): + column_list = [TaskStatusOption.name, TaskStatusOption.display_name] + + +class TaskRequirementCategoryAdmin(NamePrimaryKeyAdmin, model=TaskRequirementCategory): + pass + + +class TaskRequirementOptionAdmin(NamePrimaryKeyAdmin, model=TaskRequirementOption): + pass + + +class TeamAdmin(BaseModelView, model=Team): + column_list = [ + Team.id, + Team.name, + Team.team_email, + Team.tournament_id, + Team.captain_id, + ] + column_searchable_list = [Team.name, Team.team_email] + + +class TeamMemberAdmin(BaseModelView, model=TeamMember): + column_list = [ + TeamMember.id, + TeamMember.full_name, + TeamMember.email, + TeamMember.telegram, + TeamMember.educational_institution, + TeamMember.team_id, + ] + column_searchable_list = [ + TeamMember.full_name, + TeamMember.email, + TeamMember.telegram, + ] + + +class SubmissionAdmin(BaseModelView, model=Submission): + pass + + +class SubmissionUrlOptionAdmin(NamePrimaryKeyAdmin, model=SubmissionUrlOption): + pass + + +class SubmissionUrlAdmin(BaseModelView, model=SubmissionUrl): + pass + + +class SubmissionEvaluationAdmin(BaseModelView, model=SubmissionEvaluation): + pass + + +class JuryAssignmentAdmin(BaseModelView, model=JuryAssignment): + pass + + +class TaskEvaluationCriterionAdmin(BaseModelView, model=TaskEvaluationCriterion): + pass + + +class CriterionScoreAdmin(BaseModelView, model=CriterionScore): + pass + + +class NotificationAdmin(BaseModelView, model=Notification): + column_list = [Notification.id, Notification.user_id, Notification.body] + + +class NewsCategoryAdmin(NamePrimaryKeyAdmin, model=NewsCattegory): + pass + + +class NewsAdmin(BaseModelView, model=News): + column_list = [News.id, News.is_important, News.body] + + +class AdminAuth(AuthenticationBackend): + async def login(self, request: Request) -> bool: + form = await request.form() + id_token = str(form.get("id_token", "")).strip() + if not id_token: + return False + + try: + decoded_token = auth.verify_id_token(id_token, firebase, clock_skew_seconds=10) + session_cookie = auth.create_session_cookie( + id_token, + expires_in=settings.ADMIN_SESSION_EXPIRES, + app=firebase, + ) + except Exception: + return False + async with AsyncSessionLocal() as session: + user = await get_or_create_user_from_token(decoded_token, session) + if not user.is_admin: + return False + + request.session.update( + { + settings.ADMIN_SESSION_COOKIE_KEY: session_cookie, + "admin_user_id": user.id, + "admin_user_email": user.email, + } + ) + return True + + async def logout(self, request: Request) -> bool: + request.session.clear() + return True + + async def authenticate(self, request: Request) -> bool: + session_cookie = request.session.get(settings.ADMIN_SESSION_COOKIE_KEY) + if not session_cookie: + return False + + try: + decoded_claims = auth.verify_session_cookie( + session_cookie, + check_revoked=True, + app=firebase, + clock_skew_seconds=10, + ) + except Exception: + request.session.clear() + return False + async with AsyncSessionLocal() as session: + user = await get_or_create_user_from_token(decoded_claims, session) + if not user.is_admin: + request.session.clear() + return False + + request.state.admin_user = user + return True + + +class FrontendItemMenu(_menu.ItemMenu): + @property + def type_(self) -> str: + return "View" + + def url(self, request: Request): + return settings.FRONTEND_URL + + +def setup_admin(app): + authentication_backend = AdminAuth(secret_key=settings.SECRET_KEY) + admin = Admin( + app, + engine, + AsyncSessionLocal, + title="Tournament Admin", + templates_dir="app/admin/templates", + authentication_backend=authentication_backend, + ) + admin._menu.add(FrontendItemMenu("Back to frontend", icon="fa-solid fa-house")) + admin.templates.env.globals["firebase_config"] = { + "apiKey": settings.VITE_FIREBASE_API_KEY, + "authDomain": settings.FIREBASE_AUTH_DOMAIN, + "projectId": settings.FIREBASE_PROJECT_ID, + "storageBucket": settings.FIREBASE_STORAGE_BUCKET, + "messagingSenderId": settings.FIREBASE_MESSAGING_SENDER_ID, + "appId": settings.FIREBASE_APP_ID, + "measurementId": settings.FIREBASE_MEASUREMENT_ID, + } + admin.add_view(UserAdmin) + admin.add_view(RoleAdmin) + admin.add_view(RoleRequestAdmin) + admin.add_view(TournamentAdmin) + admin.add_view(TournamentStatusOptionAdmin) + admin.add_view(TaskAdmin) + admin.add_view(TaskStatusOptionAdmin) + admin.add_view(TaskEvaluationCriterionAdmin) + admin.add_view(TaskRequirementCategoryAdmin) + admin.add_view(TaskRequirementOptionAdmin) + admin.add_view(TeamAdmin) + admin.add_view(TeamMemberAdmin) + admin.add_view(SubmissionAdmin) + admin.add_view(SubmissionUrlOptionAdmin) + admin.add_view(SubmissionUrlAdmin) + admin.add_view(JuryAssignmentAdmin) + admin.add_view(SubmissionEvaluationAdmin) + admin.add_view(CriterionScoreAdmin) + admin.add_view(NotificationAdmin) + admin.add_view(NewsAdmin) + admin.add_view(NewsCategoryAdmin) diff --git a/backend/app/admin/templates/role_request_list.html b/backend/app/admin/templates/role_request_list.html new file mode 100644 index 0000000..09a9f32 --- /dev/null +++ b/backend/app/admin/templates/role_request_list.html @@ -0,0 +1,101 @@ +{% extends "sqladmin/list.html" %} + +{% block model_list_table %} +
+ + + + + + {% for name in model_view._list_prop_names %} + {% set label = model_view._column_labels.get(name, name) %} + + {% endfor %} + + + + {% for row in pagination.rows %} + + + + {% for name in model_view._list_prop_names %} + {% set value, formatted_value = model_view.get_list_value(row, name) %} + {% if name in model_view._relation_names %} + {% if is_list( value ) %} + + {% else %} + + {% endif %} + {% else %} + + {% endif %} + {% endfor %} + + {% endfor %} + +
+ {% if name in model_view._sort_fields %} + {% if request.query_params.get("sortBy") == name and request.query_params.get("sort") == "asc" %} + + {{ + label }} + {% elif request.query_params.get("sortBy") == name and request.query_params.get("sort") == "desc" + %} + + {{ label + }} + {% else %} + {{ label }} + {% endif %} + {% else %} + {{ label }} + {% endif %} +
+ + + + {% if model_view.check_can_view_details(request, row) %} + + + + {% endif %} + {% if model_view.check_can_edit(request, row) %} + + + + {% endif %} + {% if model_view.check_can_delete(request, row) %} + + + + + + {% endif %} + + + + + + + + {% for elem, formatted_elem in zip(value, formatted_value) %} + {% if model_view.show_compact_lists %} + ({{ formatted_elem + }}) + {% else %} + {{ formatted_elem + }}
+ {% endif %} + {% endfor %} +
{{ formatted_value + }}{{ formatted_value }}
+
+{% endblock %} \ No newline at end of file diff --git a/backend/app/admin/templates/sqladmin/login.html b/backend/app/admin/templates/sqladmin/login.html new file mode 100644 index 0000000..749ed2e --- /dev/null +++ b/backend/app/admin/templates/sqladmin/login.html @@ -0,0 +1,89 @@ +{% extends "sqladmin/base.html" %} +{% block body %} +
+
+
+
+

Login to {{ admin.title if admin else 'Admin' }}

+

Use your Firebase account. Only users with the backend + admin role can continue. +

+ +
+ + +
+ +
+ + + {% if error %} +
{{ error }}
+ {% endif %} +
+ + + + + + +
+
+
+
+ + +{% endblock %} \ No newline at end of file diff --git a/backend/app/config.py b/backend/app/config.py new file mode 100644 index 0000000..2b74e0d --- /dev/null +++ b/backend/app/config.py @@ -0,0 +1,180 @@ +import os +import json + +from pathlib import Path +from types import SimpleNamespace +from typing import Any +from pydantic import AliasChoices, BaseModel, Field +from pydantic_settings import BaseSettings, SettingsConfigDict +from datetime import timedelta + +BACKEND_DIR = Path(__file__).resolve().parent.parent +BASE_DIR = BACKEND_DIR.parent +ENV_PATH = Path(BASE_DIR, ".env") +SHARED_CONFIG_PATH = Path(BASE_DIR, "shared", "app_config.json") + + +class RoleConfig(BaseModel): + name: str + display_name: str + description: str + + +class OptionConfig(BaseModel): + name: str + display_name: str + + +class CategoryConfig(BaseModel): + name: str + main_id: str | None = None + + +class NewsCategoryConfig(OptionConfig): + categoryColor: str + + +class SharedAppConfig(BaseModel): + roles: list[RoleConfig] + tournament_statuses: list[OptionConfig] + task_statuses: list[OptionConfig] + jury_assignment_statuses: list[OptionConfig] + categories: list[CategoryConfig] + role_request_options: list[OptionConfig] + news_categories: list[NewsCategoryConfig] + + +def load_shared_app_config() -> SharedAppConfig: + with SHARED_CONFIG_PATH.open("r", encoding="utf-8") as config_file: + return SharedAppConfig.model_validate(json.load(config_file)) + + +def option_names(options: list[Any]) -> SimpleNamespace: + return SimpleNamespace( + **{option.name.upper().replace("&", "AND"): option.name for option in options} + ) + + +class Settings(BaseSettings): + model_config = SettingsConfigDict(env_file=ENV_PATH, extra="ignore") + + SECRET_KEY: str = os.getenv("SECRET_KEY") + SQLALCHEMY_DATABASE_URI: str = os.getenv("SQLALCHEMY_DATABASE_URI") + FRONTEND_URL: str = os.getenv("FRONTEND_URL") + FIREBASE_CERT_PATH: str = str(Path(BACKEND_DIR, "app", "serviceAccountKey.json")) + VITE_FIREBASE_API_KEY: str = Field(validation_alias="VITE_FIREBASE_API_KEY") + FIREBASE_AUTH_DOMAIN: str = Field( + validation_alias=AliasChoices("FIREBASE_AUTH_DOMAIN", "VITE_FIREBASE_AUTH_DOMAIN"), + ) + FIREBASE_PROJECT_ID: str = Field( + validation_alias=AliasChoices("FIREBASE_PROJECT_ID", "VITE_FIREBASE_PROJECT_ID"), + ) + FIREBASE_STORAGE_BUCKET: str = Field( + validation_alias=AliasChoices( + "FIREBASE_STORAGE_BUCKET", "VITE_FIREBASE_STORAGE_BUCKET" + ), + ) + FIREBASE_MESSAGING_SENDER_ID: str = Field( + validation_alias=AliasChoices( + "FIREBASE_MESSAGING_SENDER_ID", "VITE_FIREBASE_MESSAGING_SENDER_ID" + ), + ) + FIREBASE_APP_ID: str = Field( + validation_alias=AliasChoices("FIREBASE_APP_ID", "VITE_FIREBASE_APP_ID"), + ) + FIREBASE_MEASUREMENT_ID: str = Field( + validation_alias=AliasChoices( + "FIREBASE_MEASUREMENT_ID", "VITE_FIREBASE_MEASUREMENT_ID" + ), + ) + + ADMIN_SESSION_COOKIE_KEY: str = "admin_session_cookie" + ADMIN_SESSION_EXPIRES: timedelta = timedelta(days=5) + CHARACTERS_PER_MINUTE: int = 200 + + # TODO: Move this to a config file + CORS_ORIGINS: list[str] = [ + "http://localhost", + "http://localhost:5173", + "http://127.0.0.1:5173", + ] + ROLE_REQUEST_NOTIFICATION_MESSAGE: str = """ + A user $user requested a role $role. Do you approve this request? + """ + ROLE_REQUEST_APPROVED_MESSAGE: str = """ + Your role request was approved. The role $role was granted to you! + """ + ROLE_REQUEST_REJECTED_MESSAGE: str = """ + Your role request was rejected. You were not granted the role $role + """ + SHARED_APP_CONFIG: SharedAppConfig = Field(default_factory=load_shared_app_config) + + @property + def ROLE_OPTIONS(self) -> list[dict[str, Any]]: + return [role.model_dump() for role in self.SHARED_APP_CONFIG.roles] + + @property + def TOURNAMENT_STATUS_OPTIONS(self) -> list[dict[str, Any]]: + return [status.model_dump() for status in self.SHARED_APP_CONFIG.tournament_statuses] + + @property + def TASK_STATUS_OPTIONS(self) -> list[dict[str, Any]]: + return [status.model_dump() for status in self.SHARED_APP_CONFIG.task_statuses] + + @property + def JURY_ASSIGNMENT_STATUS_OPTIONS(self) -> list[dict[str, Any]]: + return [ + status.model_dump() for status in self.SHARED_APP_CONFIG.jury_assignment_statuses + ] + + @property + def NEWS_CATEGORY_OPTIONS(self) -> list[dict[str, Any]]: + return [ + { + "name": category.name, + "display_name": category.display_name, + } + for category in self.SHARED_APP_CONFIG.news_categories + ] + + @property + def ROLE_NAMES(self) -> SimpleNamespace: + return option_names(self.SHARED_APP_CONFIG.roles) + + @property + def TOURNAMENT_STATUS_NAMES(self) -> SimpleNamespace: + return option_names(self.SHARED_APP_CONFIG.tournament_statuses) + + @property + def TASK_STATUS_NAMES(self) -> SimpleNamespace: + return option_names(self.SHARED_APP_CONFIG.task_statuses) + + @property + def JURY_ASSIGNMENT_STATUS_NAMES(self) -> SimpleNamespace: + return option_names(self.SHARED_APP_CONFIG.jury_assignment_statuses) + + @property + def TOURNAMENT_CREATOR_ROLES(self) -> list[str]: + return [self.ROLE_NAMES.ADMIN, self.ROLE_NAMES.ORGANIZER] + + @property + def ROLE_REQUEST_OPTION_NAMES(self) -> SimpleNamespace: + return option_names(self.SHARED_APP_CONFIG.role_request_options) + + @property + def ROLE_REQUEST_INFO_OPTIONS(self) -> list[dict[str, Any]]: + return [opt.model_dump() for opt in self.SHARED_APP_CONFIG.role_request_options] + + @property + def TASK_CATEGORIES(self) -> SimpleNamespace: + return option_names(self.SHARED_APP_CONFIG.categories) + + @property + def CATEGORY_LIST(self) -> list[dict[str, Any]]: + return [cat.model_dump() for cat in self.SHARED_APP_CONFIG.categories] + + def NEWS_CATEGORY_NAMES(self) -> SimpleNamespace: + return option_names(self.SHARED_APP_CONFIG.news_categories) + + +settings = Settings() diff --git a/backend/app/core/__init__.py b/backend/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/core/seeds/__init__.py b/backend/app/core/seeds/__init__.py new file mode 100644 index 0000000..0f2eba4 --- /dev/null +++ b/backend/app/core/seeds/__init__.py @@ -0,0 +1,14 @@ +from .categories import init_categories +from .news_categories import init_news_categories +from .roles import init_roles +from .status import init_task_statuses, init_tournament_statuses +from .role_requests import init_role_request_options + + +async def init_static_data(session): + await init_roles(session) + await init_tournament_statuses(session) + await init_task_statuses(session) + await init_news_categories(session) + await init_categories(session) + await init_role_request_options(session) diff --git a/backend/app/core/seeds/categories.py b/backend/app/core/seeds/categories.py new file mode 100644 index 0000000..c375b99 --- /dev/null +++ b/backend/app/core/seeds/categories.py @@ -0,0 +1,45 @@ +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models import TaskRequirementCategory +from app.config import settings + + +async def init_categories(session: AsyncSession): + all_categories = settings.CATEGORY_LIST + + parents = [p for p in all_categories if p["main_id"] is None] + children = [c for c in all_categories if c["main_id"] is not None] + + for item in parents: + stmt = select(TaskRequirementCategory).where( + TaskRequirementCategory.name == item["name"] + ) + result = await session.execute(stmt) + if not result.scalar_one_or_none(): + session.add( + TaskRequirementCategory( + name=item["name"], display_name=item["name"], main_id=None + ) + ) + + await session.commit() + + for item in children: + stmt = select(TaskRequirementCategory).where( + TaskRequirementCategory.name == item["name"] + ) + if not (await session.execute(stmt)).scalar_one_or_none(): + p_stmt = select(TaskRequirementCategory).where( + TaskRequirementCategory.name == item["main_id"] + ) + if (await session.execute(p_stmt)).scalar_one_or_none(): + session.add( + TaskRequirementCategory( + name=item["name"], + display_name=item["name"], + main_id=item["main_id"], + ) + ) + + await session.commit() diff --git a/backend/app/core/seeds/news_categories.py b/backend/app/core/seeds/news_categories.py new file mode 100644 index 0000000..7f70848 --- /dev/null +++ b/backend/app/core/seeds/news_categories.py @@ -0,0 +1,26 @@ +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.config import settings +from app.models import NewsCattegory + + +async def init_news_categories(session: AsyncSession): + for option in settings.NEWS_CATEGORY_OPTIONS: + statement = select(NewsCattegory).where(NewsCattegory.name == option["name"]) + result = await session.execute(statement) + category = result.scalar_one_or_none() + + if category is None: + session.add( + NewsCattegory( + name=option["name"], + display_name=option["display_name"], + ) + ) + continue + + if category.display_name != option["display_name"]: + category.display_name = option["display_name"] + + await session.commit() diff --git a/backend/app/core/seeds/role_requests.py b/backend/app/core/seeds/role_requests.py new file mode 100644 index 0000000..8d240d0 --- /dev/null +++ b/backend/app/core/seeds/role_requests.py @@ -0,0 +1,24 @@ +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models import RoleRequestInfoOption +from app.config import settings + + +async def init_role_request_options(session: AsyncSession): + options = settings.ROLE_REQUEST_INFO_OPTIONS + + for opt in options: + stmt = select(RoleRequestInfoOption).where(RoleRequestInfoOption.name == opt["name"]) + result = await session.execute(stmt) + existing_option = result.scalar_one_or_none() + + if not existing_option: + session.add( + RoleRequestInfoOption(name=opt["name"], display_name=opt["display_name"]) + ) + else: + if existing_option.display_name != opt["display_name"]: + existing_option.display_name = opt["display_name"] + + await session.commit() diff --git a/backend/app/core/seeds/roles.py b/backend/app/core/seeds/roles.py new file mode 100644 index 0000000..c95d271 --- /dev/null +++ b/backend/app/core/seeds/roles.py @@ -0,0 +1,21 @@ +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.config import settings +from app.models import Role + + +async def init_roles(session: AsyncSession): + for option in settings.ROLE_OPTIONS: + statement = select(Role).where(Role.name == option["name"]) + result = await session.execute(statement) + role = result.scalar_one_or_none() + + if role is None: + session.add(Role(**option)) + continue + + role.display_name = option["display_name"] + role.description = option["description"] + + await session.commit() diff --git a/backend/app/core/seeds/status.py b/backend/app/core/seeds/status.py new file mode 100644 index 0000000..8d17817 --- /dev/null +++ b/backend/app/core/seeds/status.py @@ -0,0 +1,42 @@ +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.config import settings +from app.models import TournamentStatusOption, TaskStatusOption + + +async def _upsert_option(session: AsyncSession, model, *, name: str, display_name: str): + statement = select(model).where(model.name == name) + result = await session.execute(statement) + option = result.scalar_one_or_none() + + if option is None: + session.add(model(name=name, display_name=display_name)) + return + + if option.display_name != display_name: + option.display_name = display_name + + +async def init_tournament_statuses(session: AsyncSession): + for option in settings.TOURNAMENT_STATUS_OPTIONS: + await _upsert_option( + session, + TournamentStatusOption, + name=option["name"], + display_name=option["display_name"], + ) + + await session.commit() + + +async def init_task_statuses(session: AsyncSession): + for option in settings.TASK_STATUS_OPTIONS: + await _upsert_option( + session, + TaskStatusOption, + name=option["name"], + display_name=option["display_name"], + ) + + await session.commit() diff --git a/backend/app/core/seeds/task_requirement_options.py b/backend/app/core/seeds/task_requirement_options.py new file mode 100644 index 0000000..cc5962c --- /dev/null +++ b/backend/app/core/seeds/task_requirement_options.py @@ -0,0 +1,30 @@ +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models import TaskRequirementOption +from app.config import settings + + +async def init_task_requirement_options(session: AsyncSession): + options = settings.REQUIREMENT_OPTIONS + + for opt in options: + stmt = select(TaskRequirementOption).where(TaskRequirementOption.name == opt["name"]) + result = await session.execute(stmt) + existing_option = result.scalar_one_or_none() + + if not existing_option: + session.add( + TaskRequirementOption( + name=opt["name"], + display_name=opt["display_name"], + category_id=opt["category_id"], + ) + ) + else: + if existing_option.display_name != opt["display_name"]: + existing_option.display_name = opt["display_name"] + if existing_option.category_id != opt["category_id"]: + existing_option.category_id = opt["category_id"] + + await session.commit() diff --git a/backend/app/db.py b/backend/app/db.py new file mode 100644 index 0000000..ec642b7 --- /dev/null +++ b/backend/app/db.py @@ -0,0 +1,17 @@ +from collections.abc import AsyncGenerator +from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession + +from app.config import settings + +engine = create_async_engine( + settings.SQLALCHEMY_DATABASE_URI, + pool_pre_ping=True, + pool_recycle=3600, + connect_args={"statement_cache_size": 0}, +) +AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False) + + +async def get_session() -> AsyncGenerator[AsyncSession, None]: + async with AsyncSessionLocal() as sess: + yield sess diff --git a/backend/app/dependencies/__init__.py b/backend/app/dependencies/__init__.py new file mode 100644 index 0000000..c62deef --- /dev/null +++ b/backend/app/dependencies/__init__.py @@ -0,0 +1,14 @@ +from .session import SessionDep, get_session +from .admin_user import AdminUserDep, get_admin_user +from .current_user import ( + CurrentUserDep, + UserDep, + get_current_user, + get_user, + get_or_create_user_from_token, + current_user_dependency, +) +from .role_request import RoleRequestDep, get_role_request +from .permissions import TournamentOwnerDep, TaskOwnerDep, TeamOwnerDep, task_owner_dependency, team_owner_dependency, tournament_owner_dependency +from .jury import organizer_or_admin_dependency, assigned_jury_dependency, task_with_closed_submissions_status_dependency,\ +get_assigned_jury, get_organizer_or_admin, get_task_with_closed_submissions_status \ No newline at end of file diff --git a/backend/app/dependencies/admin_user.py b/backend/app/dependencies/admin_user.py new file mode 100644 index 0000000..5bbe3ff --- /dev/null +++ b/backend/app/dependencies/admin_user.py @@ -0,0 +1,23 @@ +from fastapi import status, HTTPException, Depends +from typing import Annotated +from sqlalchemy import select + +from .current_user import CurrentUserDep +from .session import SessionDep +from app.models import User, Role +from app.config import settings + + +async def get_admin_user(current_user: CurrentUserDep, session: SessionDep) -> User: + statement = ( + select(User) + .join(User.roles) + .where(User.id == current_user.id, Role.name == settings.ROLE_NAMES.ADMIN) + ) + user = (await session.execute(statement)).scalar() + if not user: + raise HTTPException(status.HTTP_403_FORBIDDEN, detail="Permission denied!") + return user + + +AdminUserDep = Annotated[User, Depends(get_admin_user)] diff --git a/backend/app/dependencies/current_user.py b/backend/app/dependencies/current_user.py new file mode 100644 index 0000000..19a0085 --- /dev/null +++ b/backend/app/dependencies/current_user.py @@ -0,0 +1,105 @@ +import asyncio +from typing import Annotated +from fastapi import Depends, HTTPException, status +from sqlalchemy import select, or_ +from fastapi.security import HTTPBearer +from firebase_admin import auth +from firebase_admin.auth import ( + InvalidIdTokenError, + ExpiredIdTokenError, + RevokedIdTokenError, + CertificateFetchError, + UserDisabledError, +) + +from .session import SessionDep +from app.models import User +from app.firebase import firebase + + +async def get_or_create_user_from_token(token: dict, session: SessionDep) -> User: + try: + u: auth.UserRecord = await asyncio.to_thread(auth.get_user_by_email, token["email"]) + except auth.UserNotFoundError as e: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Firebase user not found" + ) from e + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Authentication service error", + ) from e + + try: + user = await get_user(u.uid, session) + return user + except HTTPException as e: + if e.status_code == status.HTTP_404_NOT_FOUND: + if not u.display_name: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail="Full name is null!" + ) + user = User(firebase_uid=u.uid, full_name=u.display_name, email=u.email) + session.add(user) + await session.commit() + await session.refresh(user) + return user + + +async def get_current_user( + session: SessionDep, token: Annotated[HTTPBearer | str, Depends(HTTPBearer())] +) -> User: + try: + # If using http + if hasattr(token, "credentials"): + token = auth.verify_id_token(token.credentials, firebase) + # If using websockets + else: + token = auth.verify_id_token(token, firebase) + user = await get_or_create_user_from_token(token, session) + except ( + ValueError, + InvalidIdTokenError, + ExpiredIdTokenError, + RevokedIdTokenError, + CertificateFetchError, + UserDisabledError, + ) as e: + print(e) + raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail="Invalid id token!") + return user + + +# We don't use the functions defined above, because if i.e. user by email is not found and +# the function is called first, the error will be thrown. +# TODO?: Possible refactoring required because of this +async def get_user(identifier: str | int, session: SessionDep): + if isinstance(identifier, str): + statement = select(User).where( + or_(User.firebase_uid == identifier, User.email == identifier) + ) + elif isinstance(identifier, int): + statement = select(User).where(User.id == identifier) + else: + raise ValueError("Wrong user identifier type") + user = (await session.execute(statement)).scalar() + + if not user: + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found!") + return user + + +async def get_user_by_id(identifier: str, session: SessionDep) -> User: + try: + ident = int(identifier) + except ValueError: + ident = identifier + + return await get_user(ident, session) + + +current_user_dependency = Depends(get_current_user) +UserDep = Annotated[User, Depends(get_user_by_id)] +CurrentUserDep = Annotated[User, current_user_dependency] + +# It may be possible to rename the file in the future diff --git a/backend/app/dependencies/jury.py b/backend/app/dependencies/jury.py new file mode 100644 index 0000000..d08644e --- /dev/null +++ b/backend/app/dependencies/jury.py @@ -0,0 +1,41 @@ +from .current_user import CurrentUserDep +from .session import SessionDep +from app.models import User, Task +from fastapi import HTTPException, status, Depends +from app.config import settings +from app.utils import get_task + +# Permission dependencies +async def get_organizer_or_admin( + tournament_id: int, current_user: CurrentUserDep, session: SessionDep +) -> User: + from app.utils import get_tournament + + tournament = await get_tournament(tournament_id, session) + if tournament.creator_id != current_user.id and not current_user.is_admin: + raise HTTPException(status.HTTP_403_FORBIDDEN, detail="Permission denied") + return current_user + + +async def get_assigned_jury( + assignment_id: int, current_user: CurrentUserDep, session: SessionDep +) -> User: + from app.utils import get_assignment + + assignment = await get_assignment(assignment_id, session) + if assignment.jury_id != current_user.id and not current_user.is_admin: + raise HTTPException(status.HTTP_403_FORBIDDEN, detail="Permission denied") + return current_user + +async def get_task_with_closed_submissions_status(task_id: int, session: SessionDep) -> Task: + task = await get_task(task_id, session) + if task.status_id != settings.TASK_STATUS_NAMES.SUBMISSION_CLOSED: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + detail="The submissions are still open to be received!", + ) + return task + +organizer_or_admin_dependency = Depends(get_organizer_or_admin) +assigned_jury_dependency = Depends(get_assigned_jury) +task_with_closed_submissions_status_dependency = Depends(get_task_with_closed_submissions_status) diff --git a/backend/app/dependencies/permissions.py b/backend/app/dependencies/permissions.py new file mode 100644 index 0000000..7f4c695 --- /dev/null +++ b/backend/app/dependencies/permissions.py @@ -0,0 +1,82 @@ +from fastapi import Depends, HTTPException, status, Request +from typing import Annotated + +from app.models import Tournament, Task, Team +from app.utils import get_tournament, get_task, get_team +from .session import SessionDep +from .current_user import CurrentUserDep + + +class OwnershipChecker: + def __init__( + self, + object_getter: callable, + path_param: str, + tournament_id_resolver: callable = None, + ): + self.object_getter = object_getter + self.path_param = path_param + self.tournament_id_resolver = tournament_id_resolver or (lambda obj: obj.id) + + async def __call__( + self, + current_user: CurrentUserDep, + session: SessionDep, + request: Request, + ): + object_id = request.path_params.get(self.path_param) + + if object_id is None: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Path parameter '{self.path_param}' not found", + ) + + obj = await self.object_getter(int(object_id), session) + + if isinstance(obj, Tournament): + tournament = obj + else: + t_id = self.tournament_id_resolver(obj) + tournament = await get_tournament(t_id, session) + + if tournament.creator_id != current_user.id and not current_user.is_admin: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You do not have permission to perform this action", + ) + + return obj + +tournament_owner_dependency = Depends(OwnershipChecker(get_tournament, path_param="tournament_id")) + +TournamentOwnerDep = Annotated[ + Tournament, + tournament_owner_dependency +] + +task_owner_dependency = Depends( + OwnershipChecker( + get_task, + path_param="task_id", + tournament_id_resolver=lambda t: t.tournament_id, + ) +) + +TaskOwnerDep = Annotated[ + Task, + task_owner_dependency +] + +team_owner_dependency = Depends( + OwnershipChecker( + get_team, + path_param="team_id", + tournament_id_resolver=lambda t: t.tournament_id, + ) +), + +TeamOwnerDep = Annotated[ + Team, + team_owner_dependency +] diff --git a/backend/app/dependencies/role_request.py b/backend/app/dependencies/role_request.py new file mode 100644 index 0000000..86020e7 --- /dev/null +++ b/backend/app/dependencies/role_request.py @@ -0,0 +1,28 @@ +from fastapi import status, HTTPException, Depends +from typing import Annotated +from sqlalchemy import select +from sqlalchemy.orm import selectinload + +from .session import SessionDep +from app.models import RoleRequest, RoleRequestInfo + + +async def get_role_request(request_id: int, session: SessionDep) -> RoleRequest: + statement = ( + select(RoleRequest) + .options( + selectinload(RoleRequest.info).selectinload(RoleRequestInfo.option), + selectinload(RoleRequest.role), + selectinload(RoleRequest.user), + ) + .where(RoleRequest.id == request_id) + ) + result = await session.execute(statement) + request = result.scalar() + if not request: + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Role request not found!") + + return request + + +RoleRequestDep = Annotated[RoleRequest, Depends(get_role_request)] diff --git a/backend/app/dependencies/session.py b/backend/app/dependencies/session.py new file mode 100644 index 0000000..b932ca2 --- /dev/null +++ b/backend/app/dependencies/session.py @@ -0,0 +1,6 @@ +from app.db import get_session +from typing import Annotated +from fastapi import Depends +from sqlalchemy.ext.asyncio import AsyncSession + +SessionDep = Annotated[AsyncSession, Depends(get_session)] diff --git a/backend/app/firebase.py b/backend/app/firebase.py new file mode 100644 index 0000000..a1a9581 --- /dev/null +++ b/backend/app/firebase.py @@ -0,0 +1,13 @@ +from pathlib import Path +from firebase_admin import credentials, initialize_app +from app.config import settings + +firebase = None + +cert_path = Path(settings.FIREBASE_CERT_PATH) +if cert_path.exists(): + try: + cred = credentials.Certificate(settings.FIREBASE_CERT_PATH) + firebase = initialize_app(cred) + except Exception: + pass diff --git a/backend/app/init_db.py b/backend/app/init_db.py new file mode 100644 index 0000000..72286ae --- /dev/null +++ b/backend/app/init_db.py @@ -0,0 +1,20 @@ +import asyncio +from sqlalchemy.ext.asyncio import async_sessionmaker +from app.core.seeds import init_static_data +from app.models import Base +from app.db import engine + + +SessionLocal = async_sessionmaker(engine, expire_on_commit=False) + + +async def init_db() -> None: + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + + async with SessionLocal() as session: + await init_static_data(session) + + +if __name__ == "__main__": + asyncio.run(init_db()) diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py new file mode 100644 index 0000000..7923bb3 --- /dev/null +++ b/backend/app/models/__init__.py @@ -0,0 +1,22 @@ +from .base import Base +from .evaluation import ( + JuryAssignment, + JuryAssignmentStatusOption, + CriterionScore, + SubmissionEvaluation, +) +from .news import News, NewsCattegory +from .notification import Notification +from .role_request import RoleRequest, RoleRequestInfo, RoleRequestInfoOption +from .role import Role +from .submission import Submission, SubmissionUrl, SubmissionUrlOption +from .task import ( + Task, + TaskEvaluationCriterion, + TaskRequirementCategory, + TaskRequirementOption, + TaskStatusOption, +) +from .team import Team, TeamMember +from .tournament import Tournament, TournamentStatusOption +from .user import User diff --git a/backend/app/models/base.py b/backend/app/models/base.py new file mode 100644 index 0000000..a11358f --- /dev/null +++ b/backend/app/models/base.py @@ -0,0 +1,6 @@ +from sqlalchemy.orm import DeclarativeBase +from sqlalchemy.ext.asyncio import AsyncAttrs + + +class Base(AsyncAttrs, DeclarativeBase): + pass diff --git a/backend/app/models/evaluation.py b/backend/app/models/evaluation.py new file mode 100644 index 0000000..0cd3aae --- /dev/null +++ b/backend/app/models/evaluation.py @@ -0,0 +1,103 @@ +from datetime import datetime + +from sqlalchemy import ForeignKey, UniqueConstraint +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from .base import Base +from .mixin import PKMixin, DatetimeMixin, OptionMixin + + +class JuryAssignmentStatusOption(Base, OptionMixin): + __tablename__ = "jury_assignment_statuses" + jury_assignments: Mapped[list["JuryAssignment"]] = relationship( + back_populates="status", lazy="selectin", cascade="all, delete-orphan" + ) + + def __repr__(self): + return ( + f"" + ) + + +class JuryAssignment(Base, PKMixin, DatetimeMixin): + __tablename__ = "jury_assignments" + __table_args__ = (UniqueConstraint("submission_id", "jury_id"),) + + task_id: Mapped[int] = mapped_column(ForeignKey("tasks.id", ondelete="CASCADE")) + submission_id: Mapped[int] = mapped_column( + ForeignKey("submissions.team_id", ondelete="CASCADE"), nullable=False + ) + jury_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE")) + status_id: Mapped[str] = mapped_column( + ForeignKey("jury_assignment_statuses.name", ondelete="CASCADE"), + default="assigned", + ) + status: Mapped["JuryAssignmentStatusOption"] = relationship( + back_populates="jury_assignments", lazy="selectin" + ) + + task: Mapped["Task"] = relationship(back_populates="jury_assignments", lazy="selectin") + submission: Mapped["Submission"] = relationship( + back_populates="assignments", lazy="selectin" + ) + jury: Mapped["User"] = relationship(lazy="selectin") + evaluation: Mapped["SubmissionEvaluation"] = relationship( + back_populates="assignment", + lazy="selectin", + cascade="all, delete-orphan", + uselist=False, + ) + + def __repr__(self): + return ( + f"" + ) + + +class SubmissionEvaluation(Base, PKMixin, DatetimeMixin): + __tablename__ = "evaluations" + __table_args__ = (UniqueConstraint("jury_id", "submission_id"),) + + assignment_id: Mapped[int] = mapped_column( + ForeignKey("jury_assignments.id", ondelete="CASCADE"), nullable=False + ) + submission_id: Mapped[int] = mapped_column( + ForeignKey("submissions.team_id", ondelete="CASCADE"), nullable=False + ) + jury_id: Mapped[int] = mapped_column( + ForeignKey("users.id", ondelete="CASCADE"), nullable=False + ) + comment: Mapped[str | None] = mapped_column(nullable=True) + + assignment: Mapped["JuryAssignment"] = relationship( + back_populates="evaluation", lazy="selectin" + ) + submission: Mapped["Submission"] = relationship( + back_populates="evaluations", lazy="selectin" + ) + jury: Mapped["User"] = relationship(lazy="selectin") + criterion_scores: Mapped[list["CriterionScore"]] = relationship( + back_populates="evaluation", lazy="selectin", cascade="all, delete-orphan" + ) + + +class CriterionScore(Base, PKMixin): + __tablename__ = "criterion_scores" + __table_args__ = (UniqueConstraint("evaluation_id", "criterion_id"),) + + evaluation_id: Mapped[int] = mapped_column( + ForeignKey("evaluations.id", ondelete="CASCADE"), nullable=False + ) + criterion_id: Mapped[int] = mapped_column( + ForeignKey("task_evaluation_criteria.id", ondelete="CASCADE"), nullable=False + ) + score: Mapped[int] + + evaluation: Mapped["SubmissionEvaluation"] = relationship( + back_populates="criterion_scores", lazy="selectin" + ) + criterion: Mapped["TaskEvaluationCriterion"] = relationship( + back_populates="criterion_scores", lazy="selectin" + ) diff --git a/backend/app/models/mixin/__init__.py b/backend/app/models/mixin/__init__.py new file mode 100644 index 0000000..1b74150 --- /dev/null +++ b/backend/app/models/mixin/__init__.py @@ -0,0 +1,3 @@ +from .pkmixin import PKMixin +from .option_mixin import OptionMixin +from .datetime_mixin import DatetimeMixin diff --git a/backend/app/models/mixin/datetime_mixin.py b/backend/app/models/mixin/datetime_mixin.py new file mode 100644 index 0000000..37b48bc --- /dev/null +++ b/backend/app/models/mixin/datetime_mixin.py @@ -0,0 +1,10 @@ +from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy import func +from datetime import datetime + + +class DatetimeMixin: + created_at: Mapped[datetime] = mapped_column(server_default=func.now()) + updated_at: Mapped[datetime] = mapped_column( + server_default=func.now(), onupdate=func.now() + ) diff --git a/backend/app/models/mixin/option_mixin.py b/backend/app/models/mixin/option_mixin.py new file mode 100644 index 0000000..3a121aa --- /dev/null +++ b/backend/app/models/mixin/option_mixin.py @@ -0,0 +1,6 @@ +from sqlalchemy.orm import Mapped, mapped_column + + +class OptionMixin: + name: Mapped[str] = mapped_column(primary_key=True) + display_name: Mapped[str] diff --git a/backend/app/models/mixin/pkmixin.py b/backend/app/models/mixin/pkmixin.py new file mode 100644 index 0000000..9846933 --- /dev/null +++ b/backend/app/models/mixin/pkmixin.py @@ -0,0 +1,5 @@ +from sqlalchemy.orm import Mapped, mapped_column + + +class PKMixin: + id: Mapped[int] = mapped_column(primary_key=True) diff --git a/backend/app/models/news.py b/backend/app/models/news.py new file mode 100644 index 0000000..2e55a05 --- /dev/null +++ b/backend/app/models/news.py @@ -0,0 +1,48 @@ +from sqlalchemy import ForeignKey, String, event, insert +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from .base import Base +from .mixin import PKMixin, DatetimeMixin, OptionMixin +from app.config import settings + + +class NewsCattegory(Base, OptionMixin): + __tablename__ = "news_categories" + + name: Mapped[str] = mapped_column(primary_key=True, index=True) + + news: Mapped[list["News"]] = relationship(back_populates="category") + + def __repr__(self): + return f"" + + +class News(Base, PKMixin, DatetimeMixin): + __tablename__ = "news" + title: Mapped[str] = mapped_column(String(512)) + excerpt: Mapped[str] = mapped_column(String(512)) + body: Mapped[str] = mapped_column() + is_important: Mapped[bool] + category_name: Mapped[str] = mapped_column( + ForeignKey("news_categories.name", ondelete="CASCADE") + ) + + category: Mapped["NewsCattegory"] = relationship(back_populates="news", lazy="selectin") + + @property + def read_time(self): + return len(self.body) // settings.CHARACTERS_PER_MINUTE + + def __repr__(self): + body_preview = self.body[:20] + if len(self.body) > 20: + body_preview += "..." + return f"" + + +@event.listens_for(News, "after_insert") +def receive_after_insert(mapper, connection, target: News): + from .notification import Notification + + if target.is_important: + connection.execute(insert(Notification).values(body=target.excerpt, user_id=None)) diff --git a/backend/app/models/notification.py b/backend/app/models/notification.py new file mode 100644 index 0000000..b143809 --- /dev/null +++ b/backend/app/models/notification.py @@ -0,0 +1,23 @@ +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy import String, ForeignKey + +from .base import Base +from .mixin import PKMixin +from typing import Optional + + +class Notification(Base, PKMixin): + __tablename__ = "notifications" + + body: Mapped[str] = mapped_column() + user_id: Mapped[Optional[int]] = mapped_column(ForeignKey("users.id", ondelete="CASCADE")) + + user: Mapped[Optional["User"]] = relationship( + back_populates="notifications", lazy="selectin" + ) + + def __repr__(self): + body_preview = self.body[:20] + if len(self.body) > 20: + body_preview += "..." + return f"" diff --git a/backend/app/models/role.py b/backend/app/models/role.py new file mode 100644 index 0000000..8128a9f --- /dev/null +++ b/backend/app/models/role.py @@ -0,0 +1,20 @@ +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy import String +from .base import Base +from .mixin import OptionMixin +from .user import User, user_roles + + +class Role(Base, OptionMixin): + __tablename__ = "roles" + description: Mapped[str] = mapped_column() + + users: Mapped[list["User"]] = relationship( + secondary=user_roles, back_populates="roles", lazy="selectin" + ) + requests: Mapped[list["RoleRequest"]] = relationship( + back_populates="role", lazy="selectin", cascade="all, delete-orphan" + ) + + def __repr__(self): + return f"" diff --git a/backend/app/models/role_request.py b/backend/app/models/role_request.py new file mode 100644 index 0000000..228a07d --- /dev/null +++ b/backend/app/models/role_request.py @@ -0,0 +1,46 @@ +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy import ForeignKey, String + +from .base import Base +from .mixin import PKMixin, OptionMixin + + +class RoleRequest(Base, PKMixin): + __tablename__ = "role_requests" + + role_name: Mapped[str] = mapped_column(ForeignKey("roles.name", ondelete="CASCADE")) + user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE")) + + role: Mapped["Role"] = relationship(back_populates="requests", lazy="selectin") + user: Mapped["User"] = relationship(back_populates="role_requests", lazy="selectin") + info: Mapped[list["RoleRequestInfo"]] = relationship( + back_populates="request", lazy="selectin", cascade="all, delete-orphan" + ) + + def __repr__(self): + return f"" + + +class RoleRequestInfoOption(Base, OptionMixin): + __tablename__ = "role_request_info_options" + + def __repr__(self): + return f"" + + +class RoleRequestInfo(Base, PKMixin): + __tablename__ = "role_request_info" + request_id: Mapped[int] = mapped_column(ForeignKey("role_requests.id", ondelete="CASCADE")) + option_name: Mapped[str] = mapped_column( + ForeignKey("role_request_info_options.name", ondelete="CASCADE") + ) + value: Mapped[str] = mapped_column(String(4096)) + + request: Mapped[RoleRequest] = relationship(back_populates="info", lazy="selectin") + option: Mapped[RoleRequestInfoOption] = relationship(lazy="selectin") + + def __repr__(self): + value_preview = self.value[:20] + if len(self.value) > 20: + value_preview += "..." + return f"" diff --git a/backend/app/models/submission.py b/backend/app/models/submission.py new file mode 100644 index 0000000..ec78c89 --- /dev/null +++ b/backend/app/models/submission.py @@ -0,0 +1,55 @@ +from .base import Base +from .mixin import OptionMixin +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy import ForeignKey + + +class Submission(Base): + __tablename__ = "submissions" + team_id: Mapped[int] = mapped_column( + ForeignKey("teams.id", ondelete="CASCADE"), primary_key=True + ) + task_id: Mapped[int] = mapped_column( + ForeignKey("tasks.id", ondelete="CASCADE"), nullable=False + ) + + team: Mapped["Team"] = relationship( + back_populates="submission", single_parent=True, lazy="selectin" + ) + task: Mapped["Task"] = relationship(lazy="selectin") + urls: Mapped[list["SubmissionUrl"]] = relationship( + back_populates="submission", lazy="selectin", cascade="all, delete-orphan" + ) + evaluations: Mapped[list["SubmissionEvaluation"]] = relationship( + back_populates="submission", cascade="all, delete-orphan" + ) + assignments: Mapped[list["JuryAssignment"]] = relationship( + back_populates="submission", cascade="all, delete-orphan", lazy="selectin" + ) + + def __repr__(self): + return f"" + + +class SubmissionUrl(Base): + __tablename__ = "submission_urls" + submission_id: Mapped[int] = mapped_column( + ForeignKey("submissions.team_id", ondelete="CASCADE"), primary_key=True + ) + url_id: Mapped[int] = mapped_column( + ForeignKey("submission_url_options.name", ondelete="CASCADE"), primary_key=True + ) + value: Mapped[str] + + submission: Mapped["Submission"] = relationship(back_populates="urls", lazy="selectin") + url: Mapped["SubmissionUrlOption"] = relationship(lazy="selectin") + + def __repr__(self): + return f"" + + +class SubmissionUrlOption(Base, OptionMixin): + __tablename__ = "submission_url_options" + + def __repr__(self): + return f"" diff --git a/backend/app/models/task.py b/backend/app/models/task.py new file mode 100644 index 0000000..17cd09e --- /dev/null +++ b/backend/app/models/task.py @@ -0,0 +1,124 @@ +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy import ForeignKey, Table, Column + +from .base import Base +from .mixin import PKMixin, OptionMixin +from .evaluation import JuryAssignment +from datetime import datetime + +task_requirements = Table( + "task_requirements", + Base.metadata, + Column("task_id", ForeignKey("tasks.id", ondelete="CASCADE"), primary_key=True), + Column( + "requirement_id", + ForeignKey("task_requirement_options.name", ondelete="CASCADE"), + primary_key=True, + ), +) + + +class Task(Base, PKMixin): + __tablename__ = "tasks" + + title: Mapped[str] + description: Mapped[str] = mapped_column(nullable=True) + start_time: Mapped[datetime] + end_time: Mapped[datetime] + min_reviews_per_submission: Mapped[int] = mapped_column(default=1) + max_score: Mapped[int] = mapped_column(default=100) + is_leaderboard_visible: Mapped[bool] = mapped_column(default=False) + tournament_id: Mapped[int] = mapped_column( + ForeignKey( + "tournaments.id", + use_alter=True, + name="fk_task_tournament", + ondelete="CASCADE", + ) + ) + tournament: Mapped["Tournament"] = relationship( + "Tournament", + back_populates="tasks", + foreign_keys="Task.tournament_id", + lazy="selectin", + ) + status_id: Mapped[str] = mapped_column( + ForeignKey("task_statuses.name", ondelete="CASCADE") + ) + status: Mapped["TaskStatusOption"] = relationship(back_populates="tasks", lazy="selectin") + requirements: Mapped[list["TaskRequirementOption"]] = relationship( + secondary=task_requirements, lazy="selectin" + ) + criteria: Mapped[list["TaskEvaluationCriterion"]] = relationship( + back_populates="task", lazy="selectin", cascade="all, delete-orphan" + ) + jury_assignments: Mapped[list["JuryAssignment"]] = relationship( + back_populates="task", lazy="selectin", cascade="all, delete-orphan" + ) + + def __repr__(self): + return f"" + + +class TaskStatusOption(Base, OptionMixin): + __tablename__ = "task_statuses" + tasks: Mapped[list["Task"]] = relationship( + back_populates="status", lazy="selectin", cascade="all, delete-orphan" + ) + + def __repr__(self): + return f"" + + +class TaskRequirementOption(Base, OptionMixin): + __tablename__ = "task_requirement_options" + category_id: Mapped[str] = mapped_column( + ForeignKey("task_requirement_categories.name", ondelete="CASCADE") + ) + category: Mapped["TaskRequirementCategory"] = relationship( + back_populates="task_requirement_options", lazy="selectin" + ) + + def __repr__(self): + return f"" + + +class TaskRequirementCategory(Base, OptionMixin): + __tablename__ = "task_requirement_categories" + main_id: Mapped[str] = mapped_column( + ForeignKey("task_requirement_categories.name", ondelete="CASCADE"), + nullable=True, + ) + sub_categories: Mapped[list["TaskRequirementCategory"]] = relationship( + back_populates="parent_category", + lazy="selectin", + cascade="all, delete-orphan", + ) + parent_category: Mapped["TaskRequirementCategory"] = relationship( + back_populates="sub_categories", + remote_side="TaskRequirementCategory.name", + lazy="selectin", + ) + task_requirement_options: Mapped[list["TaskRequirementOption"]] = relationship( + back_populates="category", lazy="selectin", cascade="all, delete-orphan" + ) + + def __repr__(self): + return f"" + + +class TaskEvaluationCriterion(Base, PKMixin): + __tablename__ = "task_evaluation_criteria" + + task_id: Mapped[int] = mapped_column( + ForeignKey("tasks.id", ondelete="CASCADE"), nullable=False + ) + name: Mapped[str] + description: Mapped[str | None] = mapped_column(nullable=True) + weight: Mapped[int] = mapped_column(default=1) + max_score: Mapped[int] = mapped_column(default=10) + + task: Mapped["Task"] = relationship(back_populates="criteria", lazy="selectin") + criterion_scores: Mapped[list["CriterionScore"]] = relationship( + back_populates="criterion", lazy="selectin" + ) diff --git a/backend/app/models/team.py b/backend/app/models/team.py new file mode 100644 index 0000000..8a8a019 --- /dev/null +++ b/backend/app/models/team.py @@ -0,0 +1,71 @@ +from sqlalchemy import ForeignKey, UniqueConstraint +from sqlalchemy.orm import Mapped, mapped_column, relationship +from typing import Optional +from .base import Base +from .mixin import PKMixin + + +class Team(Base, PKMixin): + __tablename__ = "teams" + __table_args__ = (UniqueConstraint("contact_info", "tournament_id"),) + + name: Mapped[str] = mapped_column(nullable=False, unique=True) + team_email: Mapped[str] = mapped_column(nullable=False, unique=True) + contact_info: Mapped[str] = mapped_column(nullable=False, unique=True) + tournament_id: Mapped[int] = mapped_column( + ForeignKey("tournaments.id", ondelete="CASCADE") + ) + captain_id: Mapped[int] = mapped_column( + ForeignKey("team_members.id", ondelete="CASCADE"), nullable=True + ) + + tournament: Mapped["Tournament"] = relationship(back_populates="teams", lazy="selectin") + members: Mapped[list["TeamMember"]] = relationship( + back_populates="team", + foreign_keys="TeamMember.team_id", + cascade="all, delete-orphan", + ) + captain: Mapped["TeamMember"] = relationship( + "TeamMember", foreign_keys="Team.captain_id", post_update=True + ) + + submission: Mapped["Submission"] = relationship( + back_populates="team", cascade="all, delete-orphan" + ) + + def __repr__(self): + return f"" + + +class TeamMember(Base, PKMixin): + __tablename__ = "team_members" + + full_name: Mapped[str] = mapped_column(nullable=False) + email: Mapped[str] = mapped_column(nullable=False) + telegram: Mapped[str] = mapped_column(nullable=False) + educational_institution: Mapped[Optional[str]] = mapped_column(nullable=True) + team_id: Mapped[int] = mapped_column( + ForeignKey("teams.id", use_alter=True, name="fk_teammember_team", ondelete="CASCADE") + ) + # This field exists so we can impose contraints related to it + tournament_id: Mapped[int] = mapped_column( + ForeignKey("tournaments.id", ondelete="CASCADE") + ) + + __table_args__ = ( + UniqueConstraint("tournament_id", "email", name="uq_tournament_member_email"), + UniqueConstraint("tournament_id", "telegram", name="uq_tournament_member_telegram"), + UniqueConstraint("team_id", "email", name="uq_team_member_email"), + UniqueConstraint("team_id", "telegram", name="uq_team_member_telegram"), + ) + + team: Mapped["Team"] = relationship( + back_populates="members", foreign_keys=[team_id], lazy="selectin" + ) + + tournament: Mapped["Tournament"] = relationship( + foreign_keys=[tournament_id], lazy="selectin" + ) + + def __repr__(self): + return f"" diff --git a/backend/app/models/tournament.py b/backend/app/models/tournament.py new file mode 100644 index 0000000..033b7ba --- /dev/null +++ b/backend/app/models/tournament.py @@ -0,0 +1,118 @@ +from datetime import datetime +from typing import List +from sqlalchemy import ForeignKey, inspect, Column, Table +from sqlalchemy.orm import Mapped, mapped_column, relationship +from app.config import settings +from .base import Base +from .mixin import PKMixin, OptionMixin +from .task import Task +from .team import Team +from .submission import Submission + +tournament_juries = Table( + "tournament_juries", + Base.metadata, + Column("jury_id", ForeignKey("users.id", ondelete="CASCADE"), primary_key=True), + Column( + "tournament_id", + ForeignKey("tournaments.id", ondelete="CASCADE"), + primary_key=True, + ), +) + + +class Tournament(Base, PKMixin): + __tablename__ = "tournaments" + + title: Mapped[str] = mapped_column(nullable=False) + description: Mapped[str] + start_date: Mapped[datetime] + reg_start: Mapped[datetime] + reg_end: Mapped[datetime] + min_people_in_team: Mapped[int] + max_people_in_team: Mapped[int] + max_teams: Mapped[int] + status_id: Mapped[str] = mapped_column( + ForeignKey("tournament_status_options.name", ondelete="CASCADE") + ) + creator_id: Mapped[int] = mapped_column( + ForeignKey("users.id", ondelete="CASCADE"), nullable=False + ) + + teams: Mapped[list["Team"]] = relationship( + back_populates="tournament", lazy="selectin", cascade="all, delete-orphan" + ) + submissions: Mapped[list["Submission"]] = relationship( + "Submission", + secondary=lambda: Team.__table__, + primaryjoin=lambda: Tournament.id == Team.tournament_id, + secondaryjoin=lambda: Team.id == Submission.team_id, + viewonly=True, + lazy="selectin", + ) + tasks: Mapped[list["Task"]] = relationship( + "Task", + back_populates="tournament", + foreign_keys="Task.tournament_id", + lazy="selectin", + cascade="all, delete-orphan", + ) + juries: Mapped[list["User"]] = relationship( + back_populates="evaluates_in", lazy="selectin", secondary=tournament_juries + ) + status: Mapped["TournamentStatusOption"] = relationship( + back_populates="tournaments", lazy="selectin" + ) + creator: Mapped["User"] = relationship( + back_populates="created_tournaments", lazy="selectin" + ) + + @property + def active_task(self) -> Task | None: + tasks = self.__dict__.get("tasks") + if tasks is None: + session = inspect(self).session + if session is None: + return None + tasks = [ + task + for task in session.identity_map.values() + if isinstance(task, Task) and task.tournament_id == self.id + ] + + for task in tasks: + if task.status_id == settings.TASK_STATUS_NAMES.ACTIVE: + return task + + return None + + @property + def end_date(self) -> datetime | None: + tasks = self.__dict__.get("tasks") + if tasks is None: + session = inspect(self).session + if session is None: + return None + + tasks = [ + task + for task in session.identity_map.values() + if isinstance(task, Task) and task.tournament_id == self.id + ] + return max((task.end_time for task in tasks), default=None) + + def __repr__(self): + return f"" + + +class TournamentStatusOption(Base, OptionMixin): + __tablename__ = "tournament_status_options" + + name: Mapped[str] = mapped_column(primary_key=True, index=True) + + tournaments: Mapped[List["Tournament"]] = relationship( + back_populates="status", lazy="selectin", cascade="all, delete-orphan" + ) + + def __repr__(self): + return f"" diff --git a/backend/app/models/user.py b/backend/app/models/user.py new file mode 100644 index 0000000..2502ec8 --- /dev/null +++ b/backend/app/models/user.py @@ -0,0 +1,112 @@ +from datetime import datetime +from sqlalchemy import ForeignKey, Table, Column, func, join, or_, select +from sqlalchemy.orm import Mapped, foreign, mapped_column, relationship +from sqlalchemy.ext.hybrid import hybrid_property + +from .base import Base +from .mixin import PKMixin +from .team import Team, TeamMember +from .tournament import Tournament +from app.config import settings +from .notification import Notification +from .tournament import tournament_juries + +user_roles = Table( + "user_roles", + Base.metadata, + Column("user_id", ForeignKey("users.id", ondelete="CASCADE"), primary_key=True), + Column("role_name", ForeignKey("roles.name", ondelete="CASCADE"), primary_key=True), +) + + +class User(Base, PKMixin): + __tablename__ = "users" + # Firbase user id + firebase_uid: Mapped[str] = mapped_column(unique=True) + full_name: Mapped[str] = mapped_column(nullable=False) + email: Mapped[str] = mapped_column(nullable=False, unique=True) + created_at: Mapped[datetime] = mapped_column(server_default=func.now()) + telegram: Mapped[str] = mapped_column(nullable=True) + github: Mapped[str] = mapped_column(nullable=True) + discord: Mapped[str] = mapped_column(nullable=True) + + roles: Mapped[list["Role"]] = relationship( + secondary=user_roles, back_populates="users", lazy="selectin" + ) + notifications: Mapped[list["Notification"]] = relationship( + back_populates="user", + lazy="selectin", + cascade="all, delete-orphan", + primaryjoin=lambda: or_( + User.id == foreign(Notification.user_id), Notification.user_id == None + ), + viewonly=True, + ) + role_requests: Mapped[list["RoleRequest"]] = relationship( + back_populates="user", lazy="selectin", cascade="all, delete-orphan" + ) + created_tournaments: Mapped[list["Tournament"]] = relationship( + back_populates="creator", + lazy="selectin", + cascade="all, delete-orphan", + ) + evaluates_in: Mapped[list["Tournament"]] = relationship( + back_populates="juries", lazy="selectin", secondary=tournament_juries + ) + participates_in: Mapped[list["Tournament"]] = relationship( + "Tournament", + secondary=lambda: join( + TeamMember.__table__, Team.__table__, TeamMember.team_id == Team.id + ), + primaryjoin=lambda: User.email == foreign(TeamMember.email), + secondaryjoin=lambda: Tournament.id == foreign(Team.tournament_id), + viewonly=True, + lazy="selectin", + ) + + @hybrid_property + def is_admin(self) -> bool: + if "roles" in self.__dict__: + return any(role.name == settings.ROLE_NAMES.ADMIN for role in self.roles) + return False + + @is_admin.expression + def is_admin(cls): + return ( + select(user_roles.c.role_name) + .where(user_roles.c.user_id == cls.id, user_roles.c.role_name == settings.ROLE_NAMES.ADMIN) + ) is not None + + @hybrid_property + def is_organizer(self) -> bool: + if self.is_admin: + return True + if "roles" in self.__dict__: + return any(role.name == settings.ROLE_NAMES.ORGANIZER for role in self.roles) + return False + + @is_organizer.expression + def is_organizer(cls): + return ( + select(user_roles.c.role_name) + .where(user_roles.c.user_id == cls.id, user_roles.c.role_name == settings.ROLE_NAMES.ORGANIZER) + ) is not None + + @hybrid_property + def is_jury(self) -> bool: + if self.is_admin: + return True + if "evaluates_in" in self.__dict__: + return len(self.evaluates_in) > 0 + return False + + @is_jury.expression + def is_jury(cls): + return ( + select(func.count(tournament_juries.c.tournament_id)) + .where(tournament_juries.c.user_id == cls.id) + .label("is_jury_count") + ) > 0 + + def __repr__(self): + return f"" diff --git a/backend/app/routes/__init__.py b/backend/app/routes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/routes/jury.py b/backend/app/routes/jury.py new file mode 100644 index 0000000..daf121d --- /dev/null +++ b/backend/app/routes/jury.py @@ -0,0 +1,448 @@ +import random + +from fastapi import APIRouter, HTTPException, status +from sqlalchemy import select, update +from sqlalchemy.orm import selectinload +from app.config import settings + +from app.dependencies import ( + CurrentUserDep, + SessionDep, + current_user_dependency, + organizer_or_admin_dependency, + assigned_jury_dependency, + task_with_closed_submissions_status_dependency +) +from app.models import ( + JuryAssignment, + CriterionScore, + Submission, + SubmissionEvaluation, + SubmissionUrl, + Task, + Team, +) +from app.schemas import ( + EvaluationLeaderboardEntry, + JuryAssignmentPublic, + JuryAssignmentPublic, + SubmissionEvaluationCreate, + SubmissionEvaluationUpdate, + SubmissionEvaluationPublic, + TaskPublic, +) +from app.utils import get_task_by_tournament, get_tournament, get_assignment, get_criterion_score + +router = APIRouter(tags=["jury"]) + + +def _criterion_map(task: Task) -> dict[int, object]: + return {criterion.id: criterion for criterion in task.criteria} + + +def _calculate_evaluation_total(evaluation: SubmissionEvaluation) -> float: + weighted = 0.0 + total_weight = 0 + for item in evaluation.criterion_scores: + weight = item.criterion.weight + total_weight += weight + weighted += (item.score / item.criterion.max_score) * weight * 100 + if total_weight == 0: + return 0.0 + return round(weighted / total_weight, 2) + + +async def _task_leaderboard( + task_id: int, session: SessionDep +) -> list[EvaluationLeaderboardEntry]: + statement = ( + select(Submission) + .where(Submission.task_id == task_id) + .options( + selectinload(Submission.team), + selectinload(Submission.evaluations) + .selectinload(SubmissionEvaluation.criterion_scores) + .selectinload(CriterionScore.criterion), + ) + ) + submissions = (await session.execute(statement)).scalars().unique().all() + leaderboard = [] + for submission in submissions: + totals = [ + _calculate_evaluation_total(evaluation) for evaluation in submission.evaluations + ] + if totals: + average_score = round(sum(totals) / len(totals), 2) + total_score = round(sum(totals), 2) + else: + average_score = 0.0 + total_score = 0.0 + leaderboard.append( + EvaluationLeaderboardEntry( + submission_id=submission.team_id, + team_id=submission.team_id, + team_name=submission.team.name, + average_score=average_score, + total_score=total_score, + submitted_reviews=len(totals), + ) + ) + return sorted(leaderboard, key=lambda item: (-item.average_score, item.team_name.lower())) + + +@router.get( + "/jury/tasks/", response_model=list[TaskPublic], dependencies=[current_user_dependency] +) +async def jury_tasks(current_user: CurrentUserDep, session: SessionDep): + statement = ( + select(Task) + .join(Task.jury_assignments) + .where( + JuryAssignment.jury_id == current_user.id, + Task.status_id == settings.TASK_STATUS_NAMES.SUBMISSION_CLOSED, + ) + .options(selectinload(Task.criteria)) + .distinct() + ) + return (await session.execute(statement)).scalars().unique().all() + + +@router.get( + "/jury/tasks/{task_id}/assignments/", + response_model=list[JuryAssignmentPublic], + dependencies=[task_with_closed_submissions_status_dependency], +) +async def jury_task_assignments( + task_id: int, current_user: CurrentUserDep, session: SessionDep +): + statement = ( + select(JuryAssignment) + .join(JuryAssignment.task) + .where( + JuryAssignment.task_id == task_id, + JuryAssignment.jury_id == current_user.id, + Task.status_id == settings.TASK_STATUS_NAMES.SUBMISSION_CLOSED + ) + .options( + selectinload(JuryAssignment.status), + selectinload(JuryAssignment.task).selectinload(Task.criteria), + selectinload(JuryAssignment.submission) + .selectinload(Submission.team) + .selectinload(Team.members), + selectinload(JuryAssignment.submission) + .selectinload(Submission.team) + .selectinload(Team.tournament), + selectinload(JuryAssignment.submission) + .selectinload(Submission.urls) + .selectinload(SubmissionUrl.url), + selectinload(JuryAssignment.evaluation).selectinload( + SubmissionEvaluation.criterion_scores + ), + ) + ) + return (await session.execute(statement)).scalars().unique().all() + + +@router.get( + "/jury/assignments/{assignment_id}/", + response_model=JuryAssignmentPublic, + dependencies=[current_user_dependency, assigned_jury_dependency], +) +async def jury_assignment_detail(assignment_id: int, session: SessionDep): + assignment = await get_assignment(assignment_id, session) + if assignment.task.status_id != settings.TASK_STATUS_NAMES.SUBMISSION_CLOSED: + raise HTTPException(status.HTTP_400_NOT_FOUND, detail="Task of this assignment still accepts submissions") + return assignment + + +@router.post( + "/jury/assignments/{assignment_id}/evaluation/", + response_model=SubmissionEvaluationPublic, + status_code=status.HTTP_201_CREATED, + dependencies=[current_user_dependency, assigned_jury_dependency], +) +async def create_evaluation( + assignment_id: int, + payload: SubmissionEvaluationCreate, + current_user: CurrentUserDep, + session: SessionDep, +): + assignment = await get_assignment(assignment_id, session) + if assignment.evaluation is not None: + raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Evaluation already exists") + + criteria = _criterion_map(assignment.task) + evaluation = SubmissionEvaluation( + assignment_id=assignment.id, + submission_id=assignment.submission_id, + jury_id=current_user.id, + comment=payload.comment, + ) + for item in payload.criterion_scores: + criterion = criteria.get(item.criterion_id) + if criterion is None: + raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Criterion not found") + if item.score > criterion.max_score: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + detail=f"Score for criterion {criterion.name} exceeds max score", + ) + evaluation.criterion_scores.append(CriterionScore(**item.model_dump())) + + assignment.status_id = settings.JURY_ASSIGNMENT_STATUS_NAMES.SUBMITTED + session.add(evaluation) + await session.commit() + await session.refresh(evaluation) + return evaluation + + +@router.patch( + "/jury/assignments/{assignment_id}/evaluation/", + response_model=SubmissionEvaluationPublic, + dependencies=[current_user_dependency, assigned_jury_dependency], +) +async def update_evaluation( + assignment_id: int, + update_data: SubmissionEvaluationUpdate, + current_user: CurrentUserDep, + session: SessionDep, +): + payload = update_data.model_dump(exclude_unset=True) + assignment = await get_assignment(assignment_id, session) + if assignment.evaluation is None: + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Evaluation not found") + + criterions = _criterion_map(assignment.task) + for item in payload.pop("criterion_scores", []): + criterion = await get_criterion_score(item["criterion_id"], session) + if item["score"] > criterions[item["criterion_id"]].max_score: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + detail=f"Score for criterion {criterion.name} exceeds max score", + ) + criterion.score = item["score"] + + if payload: + result = await session.execute( + update(SubmissionEvaluation) + .where(SubmissionEvaluation.id == assignment.evaluation.id) + .values(**payload) + .returning(SubmissionEvaluation) + ) + evaluation = result.scalar_one_or_none() + if not evaluation: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Evaluation not found" + ) + await session.commit() + await session.refresh(assignment.evaluation) + return assignment.evaluation + + +@router.post( + "/tournaments/{tournament_id}/tasks/{task_id}/assignments/generate/", + response_model=list[JuryAssignmentPublic], + dependencies=[current_user_dependency, organizer_or_admin_dependency, task_with_closed_submissions_status_dependency], +) +async def generate_jury_assignments( + tournament_id: int, + task_id: int, + session: SessionDep, +): + task = await get_task_by_tournament(tournament_id, task_id, session) + tournament = task.tournament + + juries = list(tournament.juries) + submissions = ( + ( + await session.execute( + select(Submission) + .join(Submission.team) + .where(Submission.task_id == task.id, Team.tournament_id == tournament.id) + .options(selectinload(Submission.team)) + ) + ) + .scalars() + .unique() + .all() + ) + if not submissions: + raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="No submissions to assign") + if len(juries) < task.min_reviews_per_submission: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + detail="Not enough juries to satisfy minimum reviews per submission", + ) + + existing = ( + ( + await session.execute( + select(JuryAssignment).where(JuryAssignment.task_id == task.id) + ) + ) + .scalars() + .all() + ) + if existing: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + detail="Assignments already generated for this task", + ) + + jury_loads = {jury.id: 0 for jury in juries} + created = [] + randomized_submissions = list(submissions) + random.shuffle(randomized_submissions) + for submission in randomized_submissions: + available = sorted( + juries, + key=lambda jury: (jury_loads[jury.id], random.random()), + ) + selected = available[:task.min_reviews_per_submission] + for jury in selected: + assignment = JuryAssignment( + task_id=task.id, + submission_id=submission.team_id, + jury_id=jury.id, + status_id=settings.JURY_ASSIGNMENT_STATUS_NAMES.ASSIGNED, + ) + session.add(assignment) + created.append(assignment) + jury_loads[jury.id] += 1 + + await session.commit() + + # Refresh created assignments with status and evaluation relationships + created_ids = [a.id for a in created] + statement = ( + select(JuryAssignment) + .where(JuryAssignment.id.in_(created_ids)) + .options(selectinload(JuryAssignment.status), selectinload(JuryAssignment.evaluation)) + ) + result = (await session.execute(statement)).scalars().unique().all() + return result + + +@router.get( + "/tournaments/{tournament_id}/tasks/{task_id}/assignments/", + response_model=list[JuryAssignmentPublic], + dependencies=[current_user_dependency, organizer_or_admin_dependency, task_with_closed_submissions_status_dependency], +) +async def get_task_assignments( + tournament_id: int, + task_id: int, + session: SessionDep, +): + task = await get_task_by_tournament(tournament_id, task_id, session) + statement = ( + select(JuryAssignment) + .where(JuryAssignment.task_id == task.id) + .options( + selectinload(JuryAssignment.status), + selectinload(JuryAssignment.task).selectinload(Task.criteria), + selectinload(JuryAssignment.submission) + .selectinload(Submission.team) + .selectinload(Team.members), + selectinload(JuryAssignment.submission) + .selectinload(Submission.team) + .selectinload(Team.tournament), + selectinload(JuryAssignment.submission) + .selectinload(Submission.urls) + .selectinload(SubmissionUrl.url), + selectinload(JuryAssignment.evaluation).selectinload( + SubmissionEvaluation.criterion_scores + ), + ) + ) + return (await session.execute(statement)).scalars().unique().all() + + +@router.post( + "/tournaments/{tournament_id}/tasks/{task_id}/finish-evaluation/", + response_model=TaskPublic, + dependencies=[organizer_or_admin_dependency, task_with_closed_submissions_status_dependency], +) +async def finish_evaluation( + tournament_id: int, + task_id: int, + current_user: CurrentUserDep, + session: SessionDep, +): + task = await get_task_by_tournament(tournament_id, task_id, session) + + jury_assignmnents = ( + ( + await session.execute( + select(JuryAssignment).where( + JuryAssignment.task_id == task.id, + JuryAssignment.jury_id == current_user.id, + ) + ) + ) + .scalars() + .all() + ) + + for a in jury_assignmnents: + a.status_id = settings.JURY_ASSIGNMENT_STATUS_NAMES.REVIEWED + + await session.commit() + await session.refresh(task, ["jury_assignments"]) + + nonevaluated_assignments = [ + a + for a in task.jury_assignments + if a.status.name != settings.JURY_ASSIGNMENT_STATUS_NAMES.REVIEWED + ] + # If all the submissions were evaluated, mark the task as evaluated + if len(nonevaluated_assignments) == 0: + task.status_id = settings.TASK_STATUS_NAMES.EVALUATED + await session.commit() + await session.refresh(task) + return task + + +# All the leaderboard functionality is poor quality +# TODO?: refactor leaderboard functionality if we have enough time + + +@router.get( + "/tournaments/{tournament_id}/tasks/{task_id}/leaderboard/", + response_model=list[EvaluationLeaderboardEntry], +) +async def task_leaderboard(tournament_id: int, task_id: int, session: SessionDep): + task = await get_task_by_tournament(tournament_id, task_id, session) + tournament = task.tournament + if ( + not task.is_leaderboard_visible + or tournament.status_id != settings.TOURNAMENT_STATUS_NAMES.FINISHED + ): + raise HTTPException(status.HTTP_403_FORBIDDEN, detail="Leaderboard is hidden") + return await _task_leaderboard(task.id, session) + + +@router.get( + "/tournaments/{tournament_id}/leaderboard/", + response_model=list[EvaluationLeaderboardEntry], +) +async def tournament_leaderboard(tournament_id: int, session: SessionDep): + tournament = await get_tournament(tournament_id, session) + team_scores: dict[int, EvaluationLeaderboardEntry] = {} + for task in tournament.tasks: + if task.status_id != settings.TASK_STATUS_NAMES.EVALUATED: + continue + for entry in await _task_leaderboard(task.id, session): + existing = team_scores.get(entry.team_id) + if existing is None: + team_scores[entry.team_id] = entry.model_copy() + continue + existing.total_score = round(existing.total_score + entry.average_score, 2) + existing.submitted_reviews += entry.submitted_reviews + existing.average_score = round( + existing.total_score + / max(1, len([t for t in tournament.tasks if t.status_id == "evaluated"])), + 2, + ) + return sorted( + team_scores.values(), key=lambda item: (-item.total_score, item.team_name.lower()) + ) diff --git a/backend/app/routes/news.py b/backend/app/routes/news.py new file mode 100644 index 0000000..700156b --- /dev/null +++ b/backend/app/routes/news.py @@ -0,0 +1,18 @@ +from fastapi import APIRouter, status +from sqlalchemy import select +from sqlalchemy.orm import selectinload + +from app.schemas import NewsPublic +from app.dependencies import SessionDep +from app.models import News + +router = APIRouter(prefix="/news", tags=["news"]) + + +@router.get("/", response_model=list[NewsPublic], status_code=status.HTTP_200_OK) +async def get_news(session: SessionDep): + statement = ( + select(News).options(selectinload(News.category)).order_by(News.created_at.desc()) + ) + result = await session.execute(statement) + return result.scalars().all() diff --git a/backend/app/routes/profile.py b/backend/app/routes/profile.py new file mode 100644 index 0000000..bd2a4fa --- /dev/null +++ b/backend/app/routes/profile.py @@ -0,0 +1,36 @@ +from fastapi import APIRouter, status +from app.schemas import UserUpdate, CurrentUser +from app.dependencies import SessionDep, CurrentUserDep +import firebase_admin.auth as auth +from app.firebase import firebase + +router = APIRouter(prefix="/profile", tags=["profile"]) + + +@router.get("/", response_model=CurrentUser) +async def get_profile(current_user: CurrentUserDep): + return current_user + + +@router.patch("/", response_model=CurrentUser) +async def edit_profile(session: SessionDep, current_user: CurrentUserDep, user: UserUpdate): + user_data = user.model_dump(exclude_unset=True) + if "full_name" in user_data: + current_user.full_name = user_data["full_name"] + if "telegram" in user_data: + current_user.telegram = user_data["telegram"] + if "github" in user_data: + current_user.github = user_data["github"] + if "discord" in user_data: + current_user.discord = user_data["discord"] + await session.commit() + await session.refresh(current_user, attribute_names=["roles", "notifications"]) + return current_user + + +@router.delete("/", status_code=status.HTTP_204_NO_CONTENT) +async def delete_profile(session: SessionDep, current_user: CurrentUserDep): + await session.delete(current_user) + if auth.get_user(current_user.firebase_uid): + auth.delete_user(current_user.firebase_uid, firebase) + await session.commit() diff --git a/backend/app/routes/role_requests.py b/backend/app/routes/role_requests.py new file mode 100644 index 0000000..fa63c50 --- /dev/null +++ b/backend/app/routes/role_requests.py @@ -0,0 +1,108 @@ +from fastapi import APIRouter, status, HTTPException +from sqlalchemy import select + +from app.dependencies import ( + AdminUserDep, + CurrentUserDep, + RoleRequestDep, + SessionDep, + get_role_request, +) +from app.models import RoleRequest, RoleRequestInfo +from app.schemas import ( + RoleRequestPublic, + RoleRequestCreate, + UserPublic, +) +from app.utils import ( + approve_role_request_with_notification, + reject_role_request_with_notification, +) +from app.dependencies import current_user_dependency + +router = APIRouter(prefix="/role-requests", tags=["role-requests"]) + + +@router.get("/", response_model=list[RoleRequestPublic]) +async def role_requests( + session: SessionDep, + current_user: CurrentUserDep, +): + + statement = select(RoleRequest) + + if not current_user.is_admin: + statement = statement.where(RoleRequest.user_id == current_user.id) + + result = await session.execute(statement) + return result.scalars().all() + + +@router.post("/", response_model=RoleRequestPublic, status_code=status.HTTP_201_CREATED) +async def create_request( + session: SessionDep, request: RoleRequestCreate, user: CurrentUserDep +): + + statement = select(RoleRequest).where( + RoleRequest.role_name == request.role_name, + RoleRequest.user_id == user.id, + ) + existing_request = (await session.execute(statement)).scalar() + + if existing_request: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, detail="Role requests already exists!" + ) + + request_data = request.model_dump() + info = request_data.pop("info") + role_request = RoleRequest(**request_data, user_id=user.id) + session.add(role_request) + await session.flush() + + for j in info: + i = RoleRequestInfo(**j, request_id=role_request.id) + session.add(i) + + await session.commit() + return await get_role_request(role_request.id, session) + + +@router.get( + "/{request_id}/", + response_model=RoleRequestPublic, + dependencies=[current_user_dependency], +) +async def get_request(request: RoleRequestDep): + return request + + +@router.post("/{request_id}/approve/", response_model=UserPublic) +async def approve_request(request: RoleRequestDep, session: SessionDep, admin: AdminUserDep): + await approve_role_request_with_notification(request, session) + + return request.user + + +@router.post("/{request_id}/reject/", response_model=UserPublic) +async def reject_request(request: RoleRequestDep, session: SessionDep, admin: AdminUserDep): + + await reject_role_request_with_notification(request, session) + + return request.user + + +@router.delete("/{request_id}/", status_code=status.HTTP_204_NO_CONTENT) +async def delete_request( + request: RoleRequestDep, + session: SessionDep, + current_user: CurrentUserDep, +): + if request.user_id != current_user.id: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You can only delete your own requests", + ) + + await session.delete(request) + await session.commit() diff --git a/backend/app/routes/roles.py b/backend/app/routes/roles.py new file mode 100644 index 0000000..7066d11 --- /dev/null +++ b/backend/app/routes/roles.py @@ -0,0 +1,20 @@ +from fastapi import APIRouter +from app.schemas import RolePublic +from app.models import Role +from app.dependencies import SessionDep +from sqlalchemy import select +from app.utils import get_role + +router = APIRouter(prefix="/roles", tags=["roles"]) + + +@router.get("/", response_model=list[RolePublic]) +async def roles(session: SessionDep): + statement = select(Role) + roles = await session.execute(statement) + return roles.scalars().all() + + +@router.get("/{name}/", response_model=RolePublic) +async def role(name: str, session: SessionDep): + return await get_role(name, session) diff --git a/backend/app/routes/submissions.py b/backend/app/routes/submissions.py new file mode 100644 index 0000000..37e1db5 --- /dev/null +++ b/backend/app/routes/submissions.py @@ -0,0 +1,83 @@ +from fastapi import status, HTTPException +from fastapi.routing import APIRouter +from sqlalchemy import select, update +from sqlalchemy.orm import selectinload +from sqlalchemy.exc import IntegrityError + +from app.dependencies import SessionDep +from app.models import Submission, SubmissionUrl, SubmissionUrlOption, Team +from app.schemas import SubmissionModel, SubmissionUrlOptionModel, SubmissionUrlModel + +router = APIRouter(prefix="/submissions", tags=["submissions"]) + + +async def get_submission(team_id: int, session: SessionDep) -> Submission: + statement = ( + select(Submission) + .where(Submission.team_id == team_id) + .options(selectinload(Submission.urls).selectinload(SubmissionUrl.url)) + ) + result = await session.execute(statement) + submission = result.scalar_one_or_none() + + if submission is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Submission not found!" + ) + + return submission + + +@router.get("/", response_model=list[SubmissionModel], status_code=status.HTTP_200_OK) +async def submissions(session: SessionDep): + statement = select(Submission) + users = await session.execute(statement) + return users.scalars().all() + + +@router.get("/{team_id}/", response_model=SubmissionModel, status_code=status.HTTP_200_OK) +async def submission(team_id: int, session: SessionDep): + return await get_submission(team_id, session) + + +# Not ready yet + +# @router.post("/", response_model=SubmissionModel, status_code=status.HTTP_201_CREATED) +# async def create_submission(submission_data: SubmissionModel, session: SessionDep): +# team = await session.get(Team, submission_data.team_id) +# if not team: +# raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Team not found") + +# new_submission = Submission(team_id=submission_data.team_id) + +# for url_item in submission_data.urls: +# new_submission.urls.append(SubmissionUrl(url_id=url_item.url_id)) + +# session.add(new_submission) + +# try: +# await session.commit() +# await session.refresh(new_submission) +# except IntegrityError: +# await session.rollback() +# raise HTTPException( +# status_code=status.HTTP_400_BAD_REQUEST, +# detail="Submission for this team already exists", +# ) + +# return new_submission + + +# @router.patch( +# "/{team_id}/", response_model=SubmissionModel, status_code=status.HTTP_200_OK +# ) +# async def update_submission(): +# pass + + +@router.delete("/{team_id}/", status_code=status.HTTP_204_NO_CONTENT) +async def delete_submission(team_id: int, session: SessionDep): + submission = await get_submission(team_id, session) + + await session.delete(submission) + await session.commit() diff --git a/backend/app/routes/tasks.py b/backend/app/routes/tasks.py new file mode 100644 index 0000000..8c1427c --- /dev/null +++ b/backend/app/routes/tasks.py @@ -0,0 +1,174 @@ +from fastapi import status, HTTPException +from fastapi.routing import APIRouter +from sqlalchemy import select +from sqlalchemy.orm import selectinload + +from app.config import settings +from app.dependencies import SessionDep, TournamentOwnerDep, TaskOwnerDep, current_user_dependency +from app.models import Task, TaskEvaluationCriterion, Submission, SubmissionUrl, Team, Tournament +from app.schemas import TaskCreate, TaskUpdate, TaskPublic, SubmissionCreate, SubmissionModel +from app.utils import TaskStatus, update_tasks_status, get_requirements, get_task, get_team + +router = APIRouter(prefix="/tournaments/{tournament_id}/tasks", tags=["tasks"]) + + +@router.get("/", response_model=list[TaskPublic], status_code=status.HTTP_200_OK) +async def tasks(tournament_id: int, session: SessionDep): + await update_tasks_status(session) + statement = select(Task).where(Task.tournament_id == tournament_id) + result = await session.execute(statement) + return result.scalars().all() + + +@router.get("/{task_id}/", response_model=TaskPublic, status_code=status.HTTP_200_OK) +async def task(tournament_id: int, task_id: int, session: SessionDep): + task = await get_task(task_id, session) + + if task.tournament_id != tournament_id: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + detail="Task does not belong to this tournament", + ) + + TaskStatus(task).update_by_time() + await session.commit() + await session.refresh(task) + + return task + + +@router.post( + "/", + response_model=TaskPublic, + status_code=status.HTTP_201_CREATED, +) +async def create_task(tournament_id: int, task_data: TaskCreate, session: SessionDep): + task_dict = task_data.model_dump(exclude={"requirements", "criteria"}) + + if task_dict.get("start_time"): + task_dict["start_time"] = task_dict["start_time"].replace(tzinfo=None) + if task_dict.get("end_time"): + task_dict["end_time"] = task_dict["end_time"].replace(tzinfo=None) + + new_task = Task( + **task_dict, + tournament_id=tournament_id, + status_id=settings.TASK_STATUS_NAMES.DRAFT, + ) + + requirements = await get_requirements(task_data.requirements, session) + new_task.requirements = requirements + + session.add(new_task) + await session.flush() + + await session.refresh(new_task, ["criteria"]) + + for crit_data in task_data.criteria: + new_task.criteria.append( + TaskEvaluationCriterion( + task_id=new_task.id, + name=crit_data.name, + description=crit_data.description, + weight=crit_data.weight, + max_score=crit_data.max_score, + ) + ) + + await session.commit() + await session.refresh(new_task) + return new_task + + +@router.patch( + "/{task_id}/", + response_model=TaskPublic, + status_code=status.HTTP_200_OK, +) +async def update_task( + tournament_id: int, + task_data: TaskUpdate, + session: SessionDep, + task: TaskOwnerDep, +): + if task.tournament_id != tournament_id: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + detail="Task does not belong to this tournament", + ) + + update_data = task_data.model_dump( + exclude_unset=True, exclude={"requirements", "criteria"} + ) + + if not update_data and task_data.requirements is None and task_data.criteria is None: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="No fields provided for update", + ) + + if update_data.get("start_time"): + update_data["start_time"] = update_data["start_time"].replace(tzinfo=None) + if update_data.get("end_time"): + update_data["end_time"] = update_data["end_time"].replace(tzinfo=None) + + for key, value in update_data.items(): + setattr(task, key, value) + + if task_data.requirements is not None: + task.requirements = await get_requirements(task_data.requirements, session) + + if task_data.criteria is not None: + await session.refresh(task, ["criteria"]) + for crit in list(task.criteria): + await session.delete(crit) + + for crit_data in task_data.criteria: + task.criteria.append( + TaskEvaluationCriterion( + task_id=task.id, + name=crit_data.name, + description=crit_data.description, + weight=crit_data.weight, + max_score=crit_data.max_score, + ) + ) + + TaskStatus(task).update_by_time() + + await session.commit() + await session.refresh(task) + + return task + + +@router.delete( + "/{task_id}/", + status_code=status.HTTP_204_NO_CONTENT, +) +async def delete_task(session: SessionDep, task: TaskOwnerDep): + await session.delete(task) + await session.commit() + + +@router.post( + "/{task_id}/submissions/", + response_model=SubmissionModel, + status_code=status.HTTP_201_CREATED, +) +async def create_submission( + tournament_id: int, task_id: int, submission_data: SubmissionCreate, session: SessionDep +): + await get_team(submission_data.team_id, tournament_id, session) + + new_submission = Submission(team_id=submission_data.team_id, task_id=task_id) + + await session.refresh(new_submission, ["urls"]) + for item in submission_data.urls: + new_submission.urls.append(SubmissionUrl(url_id=item.url_id, value=item.value)) + + session.add(new_submission) + await session.commit() + await session.refresh(new_submission, ["urls", "team"]) + + return new_submission \ No newline at end of file diff --git a/backend/app/routes/team_members.py b/backend/app/routes/team_members.py new file mode 100644 index 0000000..1095a85 --- /dev/null +++ b/backend/app/routes/team_members.py @@ -0,0 +1,142 @@ +from fastapi import status, HTTPException +from fastapi.routing import APIRouter +from sqlalchemy import select, update +from sqlalchemy.exc import IntegrityError + +from app.dependencies import SessionDep +from app.models import TeamMember +from app.schemas import TeamMemberCreate, TeamMemberPublic, TeamMemberUpdate +from app.utils import get_team, check_registration_open, get_tournament + +router = APIRouter( + prefix="/tournaments/{tournament_id}/teams/{team_id}/members", tags=["team-members"] +) + + +async def get_team_member(member_id: int, team_id: int, session: SessionDep) -> TeamMember: + statement = select(TeamMember).where( + TeamMember.id == member_id, TeamMember.team_id == team_id + ) + + member = (await session.execute(statement)).scalar_one_or_none() + + if not member: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Team member not found!", + ) + + return member + + +@router.get("/", response_model=list[TeamMemberPublic], status_code=status.HTTP_200_OK) +async def team_members(tournament_id: int, team_id: int, session: SessionDep): + await get_team(team_id, tournament_id, session) + + statement = select(TeamMember).where(TeamMember.team_id == team_id) + result = await session.execute(statement) + return result.scalars().all() + + +@router.get("/{member_id}/", response_model=TeamMemberPublic, status_code=status.HTTP_200_OK) +async def team_member(tournament_id: int, team_id: int, member_id: int, session: SessionDep): + await get_team(team_id, tournament_id, session) + + return await get_team_member(member_id, team_id, session) + + +@router.post("/", response_model=TeamMemberPublic, status_code=status.HTTP_201_CREATED) +async def create_team_member( + tournament_id: int, team_id: int, member_data: TeamMemberCreate, session: SessionDep +): + await get_team(team_id, tournament_id, session) + + new_member = TeamMember( + **member_data.model_dump(), + team_id=team_id, + ) + + session.add(new_member) + + try: + await session.commit() + await session.refresh(new_member) + + except IntegrityError: + await session.rollback() + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Email or telegram already exists", + ) + + return new_member + + +@router.patch("/{member_id}/", response_model=TeamMemberPublic, status_code=status.HTTP_200_OK) +async def update_team_member( + tournament_id: int, + team_id: int, + member_id: int, + member_data: TeamMemberUpdate, + session: SessionDep, +): + await get_team(team_id, tournament_id, session) + + update_data = member_data.model_dump(exclude_unset=True) + + if not update_data: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail="No fields provided" + ) + + result = await session.execute( + select(TeamMember).where( + TeamMember.id == member_id, + TeamMember.team_id == team_id, + ) + ) + + member = result.scalar_one_or_none() + + if not member: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail="Team member not found" + ) + + await session.execute( + update(TeamMember) + .where( + TeamMember.id == member_id, + TeamMember.team_id == team_id, + ) + .values(**update_data) + ) + try: + await session.commit() + await session.refresh(member) + + except IntegrityError: + await session.rollback() + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Email or telegram already exists", + ) + + return member + + +@router.delete("/{member_id}/", status_code=status.HTTP_204_NO_CONTENT) +async def delete_team_member( + tournament_id: int, team_id: int, member_id: int, session: SessionDep +): + tournament = await get_tournament(tournament_id, session) + check_registration_open(tournament) + + team = await get_team(team_id, tournament_id, session) + member = await get_team_member(member_id, team_id, session) + + if member.id == team.captain_id: + raise HTTPException(400, "Cannot delete captain") + + await session.delete(member) + await session.commit() diff --git a/backend/app/routes/teams.py b/backend/app/routes/teams.py new file mode 100644 index 0000000..6b2734d --- /dev/null +++ b/backend/app/routes/teams.py @@ -0,0 +1,147 @@ +from datetime import datetime, timezone +from fastapi import status, HTTPException +from fastapi.routing import APIRouter +from sqlalchemy import select, update +from sqlalchemy.orm import selectinload +from sqlalchemy.exc import IntegrityError + +from app.dependencies import SessionDep +from app.models import Team, TeamMember +from app.schemas import TeamCreate, TeamUpdate +from app.utils import ( + get_tournament, + check_registration_open, + get_team, + validate_team_registration, +) + +router = APIRouter(prefix="/tournaments/{tournament_id}/teams", tags=["teams"]) + + +@router.get("/", response_model=list[TeamCreate], status_code=status.HTTP_200_OK) +async def teams(tournament_id: int, session: SessionDep): + await get_tournament(tournament_id, session) + + statement = ( + select(Team) + .where(Team.tournament_id == tournament_id) + .options(selectinload(Team.members), selectinload(Team.captain)) + ) + + result = await session.execute(statement) + return result.scalars().all() + + +@router.get("/{team_id}/", response_model=TeamCreate, status_code=status.HTTP_200_OK) +async def team(team_id: int, tournament_id: int, session: SessionDep): + return await get_team(team_id, tournament_id, session) + + +@router.post("/", response_model=TeamCreate, status_code=status.HTTP_201_CREATED) +async def create_team(tournament_id: int, team_data: TeamCreate, session: SessionDep): + + tournament = await get_tournament(tournament_id, session) + check_registration_open(tournament) + + await validate_team_registration(tournament, team_data, session) + + try: + new_team = Team( + name=team_data.name, + team_email=team_data.team_email, + contact_info=str(team_data.contact_info), + tournament_id=tournament_id, + ) + session.add(new_team) + await session.flush() + + captain = TeamMember( + **team_data.captain.model_dump(), + team_id=new_team.id, + ) + session.add(captain) + await session.flush() + + members = [ + TeamMember(**m.model_dump(), team_id=new_team.id) for m in team_data.members + ] + session.add_all(members) + new_team.captain_id = captain.id + await session.commit() + + except IntegrityError as e: + await session.rollback() + raise HTTPException( + status.HTTP_400_BAD_REQUEST, detail=f"Registration failed: {str(e.orig)}" + ) + + statement = ( + select(Team) + .where(Team.id == new_team.id) + .options(selectinload(Team.members), selectinload(Team.captain)) + ) + result = await session.execute(statement) + return result.scalar_one_or_none() + + +@router.patch("/{team_id}/", response_model=TeamCreate, status_code=status.HTTP_200_OK) +async def update_team( + team_id: int, tournament_id: int, team_data: TeamUpdate, session: SessionDep +): + tournament = await get_tournament(tournament_id, session) + + if datetime.now(timezone.utc) > tournament.reg_end.replace(tzinfo=timezone.utc): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Editing is forbidden after registration ends", + ) + + await get_team(team_id, tournament_id, session) + + update_data = team_data.model_dump(exclude_unset=True) + + if not update_data: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="No fields provided for update", + ) + + await session.execute( + update(Team) + .where(Team.id == team_id, Team.tournament_id == tournament_id) + .values(**update_data) + ) + + try: + await session.commit() + + except IntegrityError: + await session.rollback() + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Update violates constraints", + ) + + statement = ( + select(Team) + .where(Team.id == team_id) + .options(selectinload(Team.members), selectinload(Team.captain)) + ) + + result = await session.execute(statement) + return result.scalar_one_or_none() + + +@router.delete("/{team_id}/", status_code=status.HTTP_204_NO_CONTENT) +async def delete_team(team_id: int, tournament_id: int, session: SessionDep): + tournament = await get_tournament(tournament_id, session) + + if datetime.now(timezone.utc) > tournament.reg_end.replace(tzinfo=timezone.utc): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail="Deletion is forbidden" + ) + + team = await get_team(team_id, tournament_id, session) + + await session.delete(team) + await session.commit() diff --git a/backend/app/routes/tournaments.py b/backend/app/routes/tournaments.py new file mode 100644 index 0000000..cd3c36d --- /dev/null +++ b/backend/app/routes/tournaments.py @@ -0,0 +1,136 @@ +from fastapi import APIRouter, HTTPException, status +from sqlalchemy import select, update + +from app.schemas import ( + TournamentPublic, + TournamentCreate, + TournamentUpdate, +) +from app.config import settings +from app.models import Tournament +from app.dependencies import ( + SessionDep, + CurrentUserDep, + TournamentOwnerDep, + get_user, +) +from app.utils import get_tournament, tournament_load_options, get_status_by_name +from app.dependencies import get_user, current_user_dependency +from app.schemas import SubmissionPublic + + +router = APIRouter(prefix="/tournaments", tags=["tournaments"]) + + +@router.get("/", response_model=list[TournamentPublic], status_code=status.HTTP_200_OK) +async def tournaments(session: SessionDep): + statement = select(Tournament).options(*tournament_load_options) + tournaments = await session.execute(statement) + return tournaments.scalars().all() + + +@router.get( + "/{tournament_id}/", response_model=TournamentPublic, status_code=status.HTTP_200_OK +) +async def tournament(tournament_id: int, session: SessionDep): + return await get_tournament(tournament_id, session) + +@router.get( + "/{tournament_id}/submissions", + response_model=list[SubmissionPublic], + status_code=status.HTTP_200_OK, +) +async def submissions(tournament: TournamentOwnerDep): + return tournament.submissions + + +@router.post("/", response_model=TournamentPublic, status_code=status.HTTP_201_CREATED) +async def create_tournament( + tournament_data: TournamentCreate, + current_user: CurrentUserDep, + session: SessionDep, +): + await session.refresh(current_user, ["roles"]) + user_role_names = [role.name for role in current_user.roles] + + if not any(role in user_role_names for role in settings.TOURNAMENT_CREATOR_ROLES): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You do not have permission to create tournaments", + ) + initial_status = await get_status_by_name("draft", session) + + tournament_data = tournament_data.model_dump() + juries_ids = tournament_data.pop("juries") + + tournament = Tournament( + **tournament_data, + creator_id=current_user.id, + status_id=initial_status.name, + ) + session.add(tournament) + await session.flush() + await session.refresh(tournament, ["juries"]) + for jury_id in juries_ids: + jury = await get_user(jury_id, session) + tournament.juries.append(jury) + + await session.commit() + tournament = await get_tournament(tournament.id, session) + + return tournament + + +@router.patch( + "/{tournament_id}/", + response_model=TournamentPublic, + status_code=status.HTTP_200_OK, +) +async def update_tournament( + tournament_data: TournamentUpdate, + session: SessionDep, + tournament: TournamentOwnerDep, +): + update_data = tournament_data.model_dump(exclude_unset=True) + + if not update_data: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="No fields provided for update", + ) + await session.refresh(tournament, ["juries"]) + + juries_ids = update_data.pop("juries", []) + for jury_id in juries_ids: + jury = await get_user(jury_id, session) + tournament.juries.append(jury) + if update_data: + result = await session.execute( + update(Tournament) + .where(Tournament.id == tournament.id) + .values(**update_data) + .returning(Tournament) + ) + tournament = result.scalar_one_or_none() + + if not tournament: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Tournament not found" + ) + + await session.commit() + await session.refresh(tournament) + + return tournament + + +@router.delete( + "/{tournament_id}/", + status_code=status.HTTP_204_NO_CONTENT, +) +async def delete_tournament( + session: SessionDep, + tournament: TournamentOwnerDep, +): + await session.delete(tournament) + await session.commit() diff --git a/backend/app/routes/users.py b/backend/app/routes/users.py new file mode 100644 index 0000000..d8dc12b --- /dev/null +++ b/backend/app/routes/users.py @@ -0,0 +1,93 @@ +from fastapi import APIRouter, HTTPException, status +from sqlalchemy import select, or_, update + + +from app.schemas import UserPublic, UserUpdate, UserCreate +from app.models import User +from app.dependencies import ( + CurrentUserDep, + SessionDep, + get_user, + current_user_dependency, +) + +router = APIRouter(prefix="/users", tags=["users"]) + + +@router.get("/", response_model=list[UserPublic]) +async def users(session: SessionDep): + statement = select(User) + users = await session.execute(statement) + return users.scalars().all() + + +@router.get("/{identifier}/", response_model=UserPublic) +async def user(identifier: int | str, session: SessionDep): + return await get_user(identifier, session) + + +@router.post("/", response_model=UserPublic, dependencies=[current_user_dependency]) +async def create_user(session: SessionDep, user_create: UserCreate): + statement = select(User).where( + or_( + User.firebase_uid == user_create.firebase_uid, + User.email == user_create.email, + ) + ) + user = (await session.execute(statement)).scalar() + if not user: + user = User(**user_create.model_dump()) + session.add(user) + await session.commit() + await session.refresh(user) + return user + raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="User already exists!") + + +@router.patch( + "/{identifier}/", + response_model=UserPublic, + status_code=status.HTTP_200_OK, + dependencies=[current_user_dependency], +) +async def edit_user( + identifier: int | str, + session: SessionDep, + update_user: UserUpdate, + current_user: CurrentUserDep, +): + user = await get_user(identifier, session) + + if user.id != current_user.id and not current_user.is_admin: + raise HTTPException(status.HTTP_403_FORBIDDEN, "Permission denied") + + user_data = update_user.model_dump(exclude_unset=True) + if not user_data: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="No fields provided for update", + ) + + if isinstance(identifier, int): + condition = User.id == identifier + else: + condition = or_(User.email == identifier, User.firebase_uid == identifier) + + result = await session.execute( + update(User).where(condition).values(**user_data).returning(User) + ) + updated_user = result.scalar_one_or_none() + + await session.commit() + return updated_user + + +@router.delete( + "/{identifier}/", + status_code=status.HTTP_204_NO_CONTENT, + dependencies=[current_user_dependency], +) +async def delete_user(identifier: int | str, session: SessionDep): + user = await get_user(identifier, session) + await session.delete(user) + await session.commit() diff --git a/backend/app/schemas/__init__.py b/backend/app/schemas/__init__.py new file mode 100644 index 0000000..9e9867a --- /dev/null +++ b/backend/app/schemas/__init__.py @@ -0,0 +1,68 @@ +from .evaluation import ( + CriterionScoreCreate, + CriterionScorePublic, + EvaluationLeaderboardEntry, + FinishEvaluationResponse, + JuryAssignmentPublic, + JuryAssignmentPublic, + SubmissionEvaluationCreate, + SubmissionEvaluationPublic, + SubmissionEvaluationUpdate, +) +from .news import NewsPublic +from .notification import NotificationPublic, NotificationCreate +from .option import OptionPublic, OptionUpdate +from .role_request import RoleRequestPublic, RoleRequestCreate +from .role import RoleCreate, RolePublic, RoleUpdate +from .submission import ( + SubmissionCreateUrl, + SubmissionCreate, + SubmissionModel, + SubmissionPublic, + SubmissionUrlModel, +) +from .task_options import TaskRequirementOptionCreate, TaskRequirementOptionPublic +from .task import ( + TaskBase, + TaskCreate, + TaskEvaluationCriterionCreate, + TaskEvaluationCriterionPublic, + TaskPublic, + TaskUpdate, +) +from .team import ( + TeamCreate, + TeamPublic, + TeamUpdate, + TeamMemberCreate, + TeamMemberPublic, + TeamMemberUpdate, +) +from .tournament import ( + TournamentBase, + TournamentUpdate, + TournamentCreate, + TournamentPublic, + TournamentStatusOptionModel, + TournamentPublicMinimal, +) + +from .user import ( + UserModel, + UserPublic, + UserUpdate, + UserCreate, + CurrentUser, + UserMinimalPublic, +) +UserPublic.model_rebuild() +UserMinimalPublic.model_rebuild() +CurrentUser.model_rebuild() + +TeamPublic.model_rebuild() + +TournamentCreate.model_rebuild() +TournamentUpdate.model_rebuild() +TournamentPublicMinimal.model_rebuild() +TournamentPublic.model_rebuild() +TournamentPublicMinimal.model_rebuild() diff --git a/backend/app/schemas/datetime.py b/backend/app/schemas/datetime.py new file mode 100644 index 0000000..2d78616 --- /dev/null +++ b/backend/app/schemas/datetime.py @@ -0,0 +1,12 @@ +from pydantic import BaseModel, Field, ConfigDict +from datetime import datetime + + +class DatetimeBase(BaseModel): + model_config = ConfigDict(from_attributes=True) + created_at: datetime = Field(..., description="Created at") + updated_at: datetime = Field(..., description="Updated at") + + +class DatetimePublic(DatetimeBase): + pass diff --git a/backend/app/schemas/evaluation.py b/backend/app/schemas/evaluation.py new file mode 100644 index 0000000..afb3ebb --- /dev/null +++ b/backend/app/schemas/evaluation.py @@ -0,0 +1,83 @@ +from datetime import datetime + +from pydantic import BaseModel, ConfigDict, Field + +from .submission import SubmissionPublic +from .task import TaskPublic +from .datetime import DatetimePublic + + +class CriterionScoreBase(BaseModel): + model_config = ConfigDict(from_attributes=True) + + criterion_id: int = Field(..., gt=0) + score: int = Field(..., ge=0) + + +class CriterionScoreCreate(CriterionScoreBase): + pass + + +class CriterionScorePublic(CriterionScoreBase): + id: int + + +class SubmissionEvaluationBase(BaseModel): + comment: str | None = None + + +class SubmissionEvaluationCreate(SubmissionEvaluationBase): + criterion_scores: list[CriterionScoreCreate] = Field([], min_length=1) + + +class SubmissionEvaluationUpdate(SubmissionEvaluationBase): + comment: str | None = None + criterion_scores: list[CriterionScoreCreate] = Field([], min_length=1) + + +class SubmissionEvaluationPublic(SubmissionEvaluationBase): + id: int + assignment_id: int + submission_id: int + jury_id: int + comment: str | None + created_at: datetime | None + criterion_scores: list[CriterionScorePublic] = Field(default_factory=list) + + +class JuryAssignmentStatusPublic(BaseModel): + model_config = ConfigDict(from_attributes=True) + + name: str + display_name: str + + +class JuryAssignmentBase(BaseModel): + model_config = ConfigDict(from_attributes=True) + + id: int + task_id: int + submission_id: int + jury_id: int + status_id: str + + +class JuryAssignmentPublic(JuryAssignmentBase): + status: JuryAssignmentStatusPublic + submission: SubmissionPublic + task: TaskPublic + evaluation: SubmissionEvaluationPublic | None = None + + +class EvaluationLeaderboardEntry(BaseModel): + submission_id: int + team_id: int + team_name: str + average_score: float + total_score: float + submitted_reviews: int + + +class FinishEvaluationResponse(BaseModel): + task_id: int + status_id: str diff --git a/backend/app/schemas/news.py b/backend/app/schemas/news.py new file mode 100644 index 0000000..f979d19 --- /dev/null +++ b/backend/app/schemas/news.py @@ -0,0 +1,16 @@ +from pydantic import BaseModel, Field, ConfigDict +from .datetime import DatetimePublic + + +class NewsBase(BaseModel): + model_config = ConfigDict(from_attributes=True) + + title: str = Field(..., description="News title") + excerpt: str = Field(..., description="News excerpt") + body: str = Field(..., description="News body content") + is_important: bool = Field(default=False, description="If true, show as notification") + category_name: str = Field(..., description="News category name") + + +class NewsPublic(NewsBase, DatetimePublic): + id: int diff --git a/backend/app/schemas/notification.py b/backend/app/schemas/notification.py new file mode 100644 index 0000000..7fd0b54 --- /dev/null +++ b/backend/app/schemas/notification.py @@ -0,0 +1,24 @@ +from pydantic import BaseModel, Field, field_validator, ConfigDict + + +class NotificationBase(BaseModel): + model_config = ConfigDict(from_attributes=True) + + body: str = Field(..., description="Notification body") + user_id: int | None = Field(None, description="Notification receiver id") + + +from .user import UserPublic + + +class NotificationPublic(NotificationBase): + user: UserPublic | None = Field(None, description="Notification receiver") + + +class NotificationCreate(NotificationBase): + @field_validator("body") + @classmethod + def check_body(cls, value: str): + if not value.strip(): + raise ValueError("The body cannot be empty") + return value diff --git a/backend/app/schemas/option.py b/backend/app/schemas/option.py new file mode 100644 index 0000000..cd0ec22 --- /dev/null +++ b/backend/app/schemas/option.py @@ -0,0 +1,17 @@ +from pydantic import BaseModel, Field, ConfigDict + + +class OptionBase(BaseModel): + model_config = ConfigDict(from_attributes=True) + + name: str = Field(..., description="Option name") + display_name: str = Field(..., description="Option description") + + +class OptionUpdate(OptionBase): + name: str | None = None + display_name: str | None = None + + +class OptionPublic(OptionBase): + pass diff --git a/backend/app/schemas/role.py b/backend/app/schemas/role.py new file mode 100644 index 0000000..86e1278 --- /dev/null +++ b/backend/app/schemas/role.py @@ -0,0 +1,25 @@ +from pydantic import Field, field_validator, ConfigDict +from .option import OptionBase, OptionUpdate + + +class RoleBase(OptionBase): + model_config = ConfigDict(from_attributes=True) + + description: str = Field(..., description="Role description") + + +class RoleUpdate(OptionUpdate): + description: str | None = None + + +class RolePublic(RoleBase): + pass + + +class RoleCreate(RoleBase): + @field_validator("name") + @classmethod + def check_name(cls, value: str): + if not value.strip(): + raise ValueError("The name cannot be empty") + return value diff --git a/backend/app/schemas/role_request.py b/backend/app/schemas/role_request.py new file mode 100644 index 0000000..7e4819f --- /dev/null +++ b/backend/app/schemas/role_request.py @@ -0,0 +1,45 @@ +from pydantic import BaseModel, Field, ConfigDict + +from .user import UserPublic +from .role import RolePublic +from .option import OptionBase + + +class RoleRequestBase(BaseModel): + model_config = ConfigDict(from_attributes=True) + + role_name: str = Field(..., description="Role being requested") + + +class RoleRequestInfoOptionBase(OptionBase): + model_config = ConfigDict(from_attributes=True) + + +class RoleRequestInfoOptionPublic(RoleRequestInfoOptionBase): + pass + + +class RoleRequestInfoBase(BaseModel): + model_config = ConfigDict(from_attributes=True) + + option_name: str = Field(..., description="Request info option name") + value: str = Field(..., description="The value of the info", max_length=4096) + + +class RoleReqestInfoPublic(RoleRequestInfoBase): + id: int + request_id: int = Field(..., description="Role request id") + # request: 'RoleRequestPublic' + option: "RoleRequestInfoOptionPublic" + + +class RoleRequestPublic(RoleRequestBase): + id: int + user_id: int + user: UserPublic + role: RolePublic + info: list[RoleReqestInfoPublic] + + +class RoleRequestCreate(RoleRequestBase): + info: list[RoleRequestInfoBase] = Field(default_factory=list) diff --git a/backend/app/schemas/submission.py b/backend/app/schemas/submission.py new file mode 100644 index 0000000..68e25d8 --- /dev/null +++ b/backend/app/schemas/submission.py @@ -0,0 +1,38 @@ +from pydantic import BaseModel, Field, ConfigDict +from .team import TeamPublic + + +class SubmissionUrlOptionModel(BaseModel): + model_config = ConfigDict(from_attributes=True) + + display_name: str + + +class SubmissionUrlModel(BaseModel): + model_config = ConfigDict(from_attributes=True) + + url_id: str = Field(..., description="ID of the URL option") + url: SubmissionUrlOptionModel | None = None + value: str | None = None + + +class SubmissionCreateUrl(BaseModel): + url_id: str + value: str + + +class SubmissionCreate(BaseModel): + team_id: int + urls: list[SubmissionCreateUrl] + + +class SubmissionModel(BaseModel): + model_config = ConfigDict(from_attributes=True) + + team_id: int = Field(...) + task_id: int = Field(...) + team: TeamPublic | None = None + urls: list[SubmissionUrlModel] = Field(default_factory=list) + + +SubmissionPublic = SubmissionModel diff --git a/backend/app/schemas/task.py b/backend/app/schemas/task.py new file mode 100644 index 0000000..0a3d4cd --- /dev/null +++ b/backend/app/schemas/task.py @@ -0,0 +1,103 @@ +from datetime import datetime, timezone +from typing import Annotated +from typing_extensions import Self +from pydantic import ( + BaseModel, + Field, + ConfigDict, + field_validator, + model_validator, + AfterValidator, +) + + +def make_naive(value: datetime) -> datetime: + if value.tzinfo is not None: + return value.astimezone(timezone.utc).replace(tzinfo=None) + return value + + +NaiveDatetime = Annotated[datetime, AfterValidator(make_naive)] +StrippedStr = Annotated[str, AfterValidator(lambda v: v.strip())] + + +class TaskBase(BaseModel): + model_config = ConfigDict(from_attributes=True) + + title: StrippedStr = Field(..., min_length=3, description="Short name of the task") + description: str | None = Field( + None, description="A detailed description of what needs to be done" + ) + start_time: NaiveDatetime + end_time: NaiveDatetime + requirements: list[str] = Field(...) + + +class TaskEvaluationCriterionCreate(BaseModel): + name: str + description: str | None = None + weight: int = 1 + max_score: int = 10 + + +class TaskCreate(TaskBase): + criteria: list[TaskEvaluationCriterionCreate] = [] + + @field_validator("start_time") + @classmethod + def start_not_past(cls, value: datetime): + if value.tzinfo is None: + value = value.replace(tzinfo=timezone.utc) + + if value < datetime.now(timezone.utc): + raise ValueError("Task cannot start in the past") + return value + + @field_validator("end_time") + @classmethod + def end_make_aware(cls, value: datetime): + if value.tzinfo is None: + value = value.replace(tzinfo=timezone.utc) + return value + + @model_validator(mode="after") + def check_time_logic(self) -> Self: + if self.end_time <= self.start_time: + raise ValueError("end_time must be later than start_time") + return self + + +class TaskUpdate(BaseModel): + title: StrippedStr | None = Field(None, min_length=3) + description: str | None = None + start_time: NaiveDatetime | None = None + end_time: NaiveDatetime | None = None + requirements: list[str] | None = None + criteria: list[TaskEvaluationCriterionCreate] | None = None + + +class TaskEvaluationCriterionPublic(BaseModel): + model_config = ConfigDict(from_attributes=True) + + id: int + task_id: int + name: str + description: str | None + weight: int + max_score: int + + +class TaskPublic(TaskBase): + model_config = ConfigDict(from_attributes=True) + + id: int + tournament_id: int = Field(..., gt=0) + status_id: str = Field(...) + criteria: list["TaskEvaluationCriterionPublic"] = [] + + @field_validator("requirements", mode="before") + @classmethod + def transform_requirements(cls, value): + if isinstance(value, list) and len(value) > 0 and not isinstance(value[0], str): + return [req.name for req in value] + return value diff --git a/backend/app/schemas/task_options.py b/backend/app/schemas/task_options.py new file mode 100644 index 0000000..976d39a --- /dev/null +++ b/backend/app/schemas/task_options.py @@ -0,0 +1,13 @@ +from pydantic import BaseModel, Field + + +class TaskRequirementOptionCreate(BaseModel): + name: str = Field(..., min_length=1, description="Technical ID (e.g. “fastapi”)") + display_name: str = Field( + ..., min_length=1, description="A human-readable name (e.g., “FastAPI”)" + ) + category_id: str = Field(..., description="ID of an existing category") + + +class TaskRequirementOptionPublic(TaskRequirementOptionCreate): + pass diff --git a/backend/app/schemas/team.py b/backend/app/schemas/team.py new file mode 100644 index 0000000..3a2c600 --- /dev/null +++ b/backend/app/schemas/team.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pydantic import BaseModel, Field, EmailStr, field_validator, ConfigDict +from pydantic_extra_types.phone_numbers import PhoneNumber + +from .tournament import TournamentPublicMinimal + +if TYPE_CHECKING: + from .tournament import TournamentPublic + + +class TeamMemberBase(BaseModel): + model_config = ConfigDict(from_attributes=True) + + full_name: str = Field(..., min_length=3) + email: EmailStr = Field(..., description="Contact email") + telegram: str + educational_institution: str | None = None + + +class TeamMemberCreate(TeamMemberBase): + @field_validator("email") + @classmethod + def normalize_email(cls, value: EmailStr): + return value.lower() + + +class TeamMemberUpdate(BaseModel): + full_name: str | None = Field(None, min_length=3) + email: EmailStr | None = Field(None, description="Contact email") + telegram: str | None = None + educational_institution: str | None = None + + @field_validator("email") + @classmethod + def normalize_email(cls, value: str | None): + return value.lower().strip() if value else value + + +class TeamMemberPublic(TeamMemberBase): + pass + +class TeamBase(BaseModel): + model_config = ConfigDict(from_attributes=True) + name: str = Field(..., description="Name of the team") + team_email: EmailStr = Field(..., description="Contact email") + contact_info: PhoneNumber = Field(..., description="Phone number") + + +class TeamCreate(TeamBase): + captain: TeamMemberPublic + members: list[TeamMemberPublic] = Field(..., min_length=1) + + @field_validator("team_email") + @classmethod + def normalize_email(cls, value: str): + return value.lower().strip() + + +class TeamUpdate(BaseModel): + name: str | None = None + team_email: EmailStr | None = None + contact_info: PhoneNumber | None = None + + @field_validator("team_email") + @classmethod + def normalize_email(cls, value: str | None): + return value.lower().strip() if value else value + + +class TeamPublic(TeamBase): + tournament: "TournamentPublicMinimal" + members: list[TeamMemberPublic] diff --git a/backend/app/schemas/tournament.py b/backend/app/schemas/tournament.py new file mode 100644 index 0000000..868baa9 --- /dev/null +++ b/backend/app/schemas/tournament.py @@ -0,0 +1,125 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Annotated +from datetime import datetime, timezone +from pydantic import ( + AfterValidator, + BaseModel, + ConfigDict, + Field, + AliasPath, + model_validator, +) + +from .option import OptionPublic +from .task import TaskPublic + +if TYPE_CHECKING: + from .team import TeamPublic + from .user import UserMinimalPublic + + +def make_naive(value: datetime) -> datetime: + if value.tzinfo is not None: + return value.astimezone(timezone.utc).replace(tzinfo=None) + return value + + +def drop_time(date: datetime) -> datetime: + """Обнуляє час до 00:00:00 для порівняння лише дат.""" + return date.replace(hour=0, minute=0, second=0, microsecond=0) + + +NaiveDatetime = Annotated[datetime, AfterValidator(make_naive)] +StrippedStr = Annotated[str, AfterValidator(lambda v: v.strip())] + + +class TournamentBase(BaseModel): + title: StrippedStr = Field(..., min_length=3) + description: str + start_date: NaiveDatetime + reg_start: NaiveDatetime + reg_end: NaiveDatetime + min_people_in_team: int = Field(..., gt=0) + max_people_in_team: int = Field(..., gt=0) + max_teams: int = Field(..., gt=0) + + +class TournamentCreate(TournamentBase): + juries: list[int | str] = Field(..., description="Jury ids") + + @model_validator(mode="after") + def validate_dates(self) -> "TournamentCreate": + now = drop_time(datetime.now(timezone.utc).replace(tzinfo=None)) + + if drop_time(self.reg_start) < now: + raise ValueError("Registration cannot start in the past") + + if self.reg_end <= self.reg_start: + raise ValueError("Registration end must be later than start") + + if self.start_date <= self.reg_end: + raise ValueError("Tournament must start after registration ends") + + return self + + +class TournamentUpdate(BaseModel): + title: StrippedStr | None = Field(None, min_length=3) + description: str | None = None + start_date: NaiveDatetime | None = None + reg_start: NaiveDatetime | None = None + reg_end: NaiveDatetime | None = None + max_teams: int | None = Field(None, gt=0) + juries: list[int | str] | None = Field(None, description="Jury ids") + + @model_validator(mode="after") + def validate_dates_update(self) -> "TournamentUpdate": + now = drop_time(datetime.now(timezone.utc).replace(tzinfo=None)) + + if self.reg_start is not None and drop_time(self.reg_start) < now: + raise ValueError("Registration start cannot be in the past") + + if self.reg_end is not None and drop_time(self.reg_end) < now: + raise ValueError("Registration end cannot be in the past") + + if self.start_date is not None and drop_time(self.start_date) < now: + raise ValueError("Tournament start cannot be in the past") + + if self.reg_start and self.reg_end: + if self.reg_end <= self.reg_start: + raise ValueError("Registration end must be later than start") + + if self.start_date and self.reg_end: + if self.start_date <= self.reg_end: + raise ValueError("Tournament must start after registration ends") + + return self + + +class TournamentStatusOptionModel(BaseModel): + model_config = ConfigDict(from_attributes=True) + name: StrippedStr = Field(..., min_length=3) + + +class TournamentPublic(TournamentBase): + model_config = ConfigDict(from_attributes=True) + + id: int + end_date: datetime | None + creator: "UserMinimalPublic" + status: OptionPublic + tasks: list[TaskPublic] + active_task: TaskPublic | None + teams: list[TeamPublic] + juries: list["UserMinimalPublic"] + status_name: str = Field(validation_alias=AliasPath("status", "display_name")) + + +class TournamentPublicMinimal(TournamentBase): + model_config = ConfigDict(from_attributes=True) + + id: int + end_date: datetime | None + status: OptionPublic + status_name: str = Field(validation_alias=AliasPath("status", "display_name")) \ No newline at end of file diff --git a/backend/app/schemas/user.py b/backend/app/schemas/user.py new file mode 100644 index 0000000..2666c77 --- /dev/null +++ b/backend/app/schemas/user.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pydantic import BaseModel, Field, EmailStr, field_validator, ConfigDict +from .role import RolePublic +from .tournament import TournamentPublicMinimal + +if TYPE_CHECKING: + from .notification import NotificationPublic + + +class UserBase(BaseModel): + model_config = ConfigDict(from_attributes=True) + + full_name: str = Field(..., description="Username") + + +class UserCreate(UserBase): + firebase_uid: str = Field(..., description="Firebase user id") + email: EmailStr = Field(..., description="Email") + + @field_validator("full_name") + @classmethod + def check_name(cls, value: str): + if not value.strip(): + raise ValueError("The name cannot be empty") + return value + + +class UserUpdate(UserBase): + full_name: str | None = None + email: str | None = None + telegram: str | None = None + github: str | None = None + discord: str | None = None + + +class UserMinimalPublic(UserBase): + """Minimal user schema without circular relationships for use in nested contexts""" + + id: int + email: EmailStr + firebase_uid: str + roles: list[RolePublic] + telegram: str | None + github: str | None + discord: str | None + + is_admin: bool + is_organizer: bool + is_jury: bool + + +class UserPublic(UserBase): + id: int + email: EmailStr + firebase_uid: str + roles: list[RolePublic] + telegram: str | None + github: str | None + discord: str | None + is_admin: bool + is_organizer: bool + is_jury: bool + evaluates_in: list["TournamentPublicMinimal"] + + +class UserModel(UserBase): + email: EmailStr = Field(..., description="User email") + + @field_validator("email") + @classmethod + def check_email(cls, value: str): + return value.lower().strip() + + +# Return notifications of current user only +class CurrentUser(UserPublic): + notifications: list["NotificationPublic"] + participates_in: list["TournamentPublicMinimal"] = Field(default_factory=list) + created_tournaments: list["TournamentPublicMinimal"] diff --git a/backend/app/utils/__init__.py b/backend/app/utils/__init__.py new file mode 100644 index 0000000..28919dc --- /dev/null +++ b/backend/app/utils/__init__.py @@ -0,0 +1,9 @@ +from .routes import * +from .fsm import * +from .notifications import send_notification +from .users import get_user_by_email, get_user_by_firebase_uid, get_user_by_id +from .tasks import get_requirements, get_task, get_task_by_tournament +from .tournaments import get_tournament, tournament_load_options +from .roles import get_role +from .assignments import get_assignment +from .criterions import get_criterion_score, get_task_evaluation_criterion diff --git a/backend/app/utils/assignments.py b/backend/app/utils/assignments.py new file mode 100644 index 0000000..10bc9cd --- /dev/null +++ b/backend/app/utils/assignments.py @@ -0,0 +1,39 @@ +from sqlalchemy import select +from sqlalchemy.orm import selectinload +from app.models import ( + JuryAssignment, + Task, + Submission, + Team, + SubmissionEvaluation, + SubmissionUrl, +) +from app.dependencies import SessionDep +from fastapi import HTTPException, status + + +async def get_assignment(assignment_id: int, session: SessionDep) -> JuryAssignment: + statement = ( + select(JuryAssignment) + .where(JuryAssignment.id == assignment_id) + .options( + selectinload(JuryAssignment.status), + selectinload(JuryAssignment.task).selectinload(Task.criteria), + selectinload(JuryAssignment.submission) + .selectinload(Submission.team) + .selectinload(Team.members), + selectinload(JuryAssignment.submission) + .selectinload(Submission.team) + .selectinload(Team.tournament), + selectinload(JuryAssignment.submission) + .selectinload(Submission.urls) + .selectinload(SubmissionUrl.url), + selectinload(JuryAssignment.evaluation).selectinload( + SubmissionEvaluation.criterion_scores + ), + ) + ) + assignment = (await session.execute(statement)).scalar_one_or_none() + if assignment is None: + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Assignment not found") + return assignment diff --git a/backend/app/utils/criterions.py b/backend/app/utils/criterions.py new file mode 100644 index 0000000..b25d9aa --- /dev/null +++ b/backend/app/utils/criterions.py @@ -0,0 +1,21 @@ +from sqlalchemy import select, or_ +from app.models import CriterionScore, TaskEvaluationCriterion +from app.dependencies import SessionDep +from fastapi import HTTPException, status + + +async def get_criterion_score(criterion_id: int, session: SessionDep) -> CriterionScore: + statement = select(CriterionScore).where(CriterionScore.criterion_id == criterion_id) + + criterion = (await session.execute(statement)).scalar() + if not criterion: + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Criterion not found!") + return criterion + +async def get_task_evaluation_criterion(identifier: str | int, session: SessionDep) -> TaskEvaluationCriterion: + statement = select(TaskEvaluationCriterion).where(or_(TaskEvaluationCriterion.name == identifier, TaskEvaluationCriterion.id == identifier)) + + criterion = (await session.execute(statement)).scalar() + if not criterion: + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Criterion not found!") + return criterion \ No newline at end of file diff --git a/backend/app/utils/fsm/__init__.py b/backend/app/utils/fsm/__init__.py new file mode 100644 index 0000000..e3ea166 --- /dev/null +++ b/backend/app/utils/fsm/__init__.py @@ -0,0 +1,2 @@ +from .task_status import TaskStatus, update_tasks_status +from .tournament_status import auto_update_tournament_status, get_status_by_name diff --git a/backend/app/utils/fsm/task_status.py b/backend/app/utils/fsm/task_status.py new file mode 100644 index 0000000..4e7627d --- /dev/null +++ b/backend/app/utils/fsm/task_status.py @@ -0,0 +1,67 @@ +from datetime import datetime, timezone +from sqlalchemy import select +from statemachine import StateMachine, State + +from app.config import settings +from app.models import Task +from app.dependencies import SessionDep + + +class TaskStatus(StateMachine): + draft = State("Draft", value=settings.TASK_STATUS_NAMES.DRAFT, initial=True) + active = State("Active", value=settings.TASK_STATUS_NAMES.ACTIVE) + submission_closed = State( + "SubmissionClosed", value=settings.TASK_STATUS_NAMES.SUBMISSION_CLOSED + ) + evaluated = State("Evaluated", value=settings.TASK_STATUS_NAMES.EVALUATED, final=True) + + start = draft.to(active) + reset_to_draft = active.to(draft) + close = active.to(submission_closed) + evaluate = submission_closed.to(evaluated) + + def __init__(self, task: Task): + self.task = task + super().__init__(model=self.task, state_field="status_id") + + def update_by_time(self): + + now = datetime.now(timezone.utc).replace(tzinfo=None) + + start = self.task.start_time + end = self.task.end_time + + changed = False + if self.current_state == self.active and now < start: + self.reset_to_draft() + changed = True + + elif self.current_state == self.draft and now >= start: + self.start() + changed = True + + elif self.current_state == self.active and now > end: + self.close() + changed = True + + return changed + + +async def update_tasks_status(session: SessionDep): + statement = select(Task).where(Task.status_id != settings.TASK_STATUS_NAMES.EVALUATED) + result = await session.execute(statement) + tasks = result.scalars().all() + + changed = False + + for task in tasks: + fsm = TaskStatus(task) + if fsm.update_by_time(): + if task.start_time and task.start_time.tzinfo: + task.start_time = task.start_time.replace(tzinfo=None) + if task.end_time and task.end_time.tzinfo: + task.end_time = task.end_time.replace(tzinfo=None) + changed = True + + if changed: + await session.commit() diff --git a/backend/app/utils/fsm/tournament_status.py b/backend/app/utils/fsm/tournament_status.py new file mode 100644 index 0000000..e6f5b74 --- /dev/null +++ b/backend/app/utils/fsm/tournament_status.py @@ -0,0 +1,74 @@ +from datetime import datetime, timezone +from fastapi import HTTPException, status +from sqlalchemy import select + + +from app.config import settings +from app.dependencies.session import SessionDep +from app.models import Tournament, TournamentStatusOption +from statemachine import StateMachine, State + + +class TournamentStatus(StateMachine): + draft = State("Draft", value=settings.TOURNAMENT_STATUS_NAMES.DRAFT, initial=True) + registration = State("Registration", value=settings.TOURNAMENT_STATUS_NAMES.REGISTRATION) + running = State("Running", value=settings.TOURNAMENT_STATUS_NAMES.RUNNING) + finished = State("Finished", value=settings.TOURNAMENT_STATUS_NAMES.FINISHED, final=True) + + start_registration = draft.to(registration) + start_tournament = registration.to(running) + finish_tournament = running.to(finished) + + def __init__(self, tournament: Tournament, session): + self.tournament = tournament + self.session = session + self.tournament.status_name = tournament.status.name + + super().__init__(model=self.tournament, state_field="status_name") + + async def update_db_status(self): + + new_status_name = self.current_state_value + + statement = select(TournamentStatusOption).where( + TournamentStatusOption.name == new_status_name + ) + result = await self.session.execute(statement) + status_option = result.scalar_one() + + self.tournament.status_id = status_option.name + self.tournament.status = status_option + + await self.session.commit() + + +async def auto_update_tournament_status(tournament: Tournament, session): + now = datetime.now(timezone.utc).replace(tzinfo=None) + + fsm = TournamentStatus(tournament, session) + initial_state_value = fsm.current_state_value + + if fsm.configuration == TournamentStatus.draft: + if tournament.reg_start <= now: + fsm.start_registration() + + if fsm.configuration == TournamentStatus.registration: + if tournament.reg_end <= now: + fsm.start_tournament() + + if fsm.current_state_value != initial_state_value: + await fsm.update_db_status() + + await session.refresh(tournament, ["status"]) + + +async def get_status_by_name(name: str, session: SessionDep) -> TournamentStatusOption: + statement = select(TournamentStatusOption).where(TournamentStatusOption.name == name) + status_ = (await session.execute(statement)).scalar_one_or_none() + + if not status_: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail="Status not found!" + ) + + return status_ diff --git a/backend/app/utils/notifications.py b/backend/app/utils/notifications.py new file mode 100644 index 0000000..daa8df3 --- /dev/null +++ b/backend/app/utils/notifications.py @@ -0,0 +1,48 @@ +from string import Template +from sqlalchemy import select +from sqlalchemy.orm import selectinload + +from app.dependencies import SessionDep +from app.models import Notification, User +from app.schemas import NotificationCreate, NotificationPublic + + +async def send_notification( + notification: NotificationCreate, session: SessionDep, **kwargs +) -> Notification: + from app import app + from app.websockets import sio + + notification = NotificationCreate.model_validate(notification) + try: + notification.body = str(Template(notification.body).substitute(**kwargs)) + except KeyError as e: + raise ValueError("Notification body placeholder was not provided!") from e + except ValueError as err: + raise ValueError("Notification body template is invalid!") from err + + notification = Notification(**notification.model_dump()) + session.add(notification) + await session.commit() + await session.refresh(notification) + + user_result = await session.execute( + select(User).options(selectinload(User.roles)).where(User.id == notification.user_id) + ) + user = user_result.scalar_one() + payload = NotificationPublic.model_validate( + { + "body": notification.body, + "user_id": notification.user_id, + "user": user, + } + ) + + try: + user_sid = app.state.user_websocket_sessions[notification.user_id]["sid"] + await sio.emit("notification", payload.model_dump(), user_sid) + except KeyError: + # If the user is offline, don't send the event. + pass + + return notification diff --git a/backend/app/utils/roles.py b/backend/app/utils/roles.py new file mode 100644 index 0000000..91f0f60 --- /dev/null +++ b/backend/app/utils/roles.py @@ -0,0 +1,12 @@ +from fastapi import HTTPException, status +from sqlalchemy import select +from app.dependencies.session import SessionDep +from app.models import Role + + +async def get_role(name: str, session: SessionDep): + statement = select(Role).where(Role.name == name) + role = (await session.execute(statement)).scalar() + if not role: + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Role not found!") + return role diff --git a/backend/app/utils/routes/__init__.py b/backend/app/utils/routes/__init__.py new file mode 100644 index 0000000..c4276d6 --- /dev/null +++ b/backend/app/utils/routes/__init__.py @@ -0,0 +1,11 @@ +from .teams import ( + check_registration_open, + get_team, + validate_team_registration, +) +from .role_requests import ( + approve_role_request, + reject_role_request, + approve_role_request_with_notification, + reject_role_request_with_notification, +) diff --git a/backend/app/utils/routes/dates_logic.py b/backend/app/utils/routes/dates_logic.py new file mode 100644 index 0000000..7f3b5f0 --- /dev/null +++ b/backend/app/utils/routes/dates_logic.py @@ -0,0 +1,63 @@ +from datetime import datetime, timezone +from fastapi import HTTPException, status + + +def validate_dates_on_create(start_date, reg_start, reg_end): + now = datetime.now() + + if reg_start < now: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Registration cannot start in the past", + ) + + if reg_end <= reg_start: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Registration end must be later than start", + ) + + if start_date <= reg_end: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Tournament must start after registration ends", + ) + + +def validate_dates_on_update( + *, + start_date, + reg_start, + reg_end, +): + now = datetime.now() + + if reg_start < now: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Registration start cannot be in the past", + ) + + if reg_end < now: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Registration end cannot be in the past", + ) + + if start_date < now: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Tournament start cannot be in the past", + ) + + if reg_end <= reg_start: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Registration end must be later than start", + ) + + if start_date <= reg_end: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Tournament must start after registration ends", + ) diff --git a/backend/app/utils/routes/role_requests.py b/backend/app/utils/routes/role_requests.py new file mode 100644 index 0000000..7676c0c --- /dev/null +++ b/backend/app/utils/routes/role_requests.py @@ -0,0 +1,33 @@ +from app.models import RoleRequest +from app.schemas import NotificationCreate +from app.config import settings +from app.dependencies import SessionDep +from ..notifications import send_notification + + +async def approve_role_request(request: RoleRequest, session: SessionDep) -> None: + request.user.roles.append(request.role) + await session.delete(request) + await session.commit() + await session.refresh(request.user) + + +async def approve_role_request_with_notification(request: RoleRequest, session: SessionDep): + await approve_role_request(request, session) + notification = NotificationCreate( + body=settings.ROLE_REQUEST_APPROVED_MESSAGE, user_id=request.user.id + ) + await send_notification(notification, session, role=request.role.name) + + +async def reject_role_request(request: RoleRequest, session: SessionDep) -> None: + await session.delete(request) + await session.commit() + + +async def reject_role_request_with_notification(request: RoleRequest, session: SessionDep): + await reject_role_request(request, session) + notification = NotificationCreate( + body=settings.ROLE_REQUEST_REJECTED_MESSAGE, user_id=request.user.id + ) + await send_notification(notification, session, role=request.role.name) diff --git a/backend/app/utils/routes/teams.py b/backend/app/utils/routes/teams.py new file mode 100644 index 0000000..77117f4 --- /dev/null +++ b/backend/app/utils/routes/teams.py @@ -0,0 +1,64 @@ +from datetime import datetime, timezone +from fastapi import status, HTTPException +from sqlalchemy import select, func +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import selectinload + + +from app.dependencies import SessionDep +from app.models import Team, Tournament +from app.schemas import TeamCreate + + +def check_registration_open(tournament: Tournament): + now = datetime.now(timezone.utc) + + reg_start = tournament.reg_start.replace(tzinfo=timezone.utc) + reg_end = tournament.reg_end.replace(tzinfo=timezone.utc) + + if not (reg_start <= now <= reg_end): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Registration is closed", + ) + + +async def get_team(team_id: int, tournament_id: int, session: SessionDep) -> Team: + statement = ( + select(Team) + .where( + Team.id == team_id, + Team.tournament_id == tournament_id, + ) + .options( + selectinload(Team.members), + selectinload(Team.captain), + ) + ) + + result = await session.execute(statement) + team = result.scalar_one_or_none() + + if not team: + raise HTTPException( + status.HTTP_404_NOT_FOUND, + detail="Team not found!", + ) + + return team + + +async def validate_team_registration( + tournament: Tournament, team_data: TeamCreate, session: AsyncSession +): + + count_stmt = select(func.count()).where(Team.tournament_id == tournament.id) + teams_count = (await session.execute(count_stmt)).scalar() + if teams_count >= tournament.max_teams: + raise HTTPException(status.HTTP_400_BAD_REQUEST, "Tournament is full") + + all_emails = [team_data.captain.email] + [m.email for m in team_data.members] + if len(all_emails) != len(set(all_emails)): + raise HTTPException( + status.HTTP_400_BAD_REQUEST, "Emails must be unique inside team" + ) diff --git a/backend/app/utils/tasks.py b/backend/app/utils/tasks.py new file mode 100644 index 0000000..eef001c --- /dev/null +++ b/backend/app/utils/tasks.py @@ -0,0 +1,50 @@ +from fastapi import HTTPException, status +from sqlalchemy import select +from sqlalchemy.orm import selectinload +from app.dependencies.session import SessionDep +from app.models import Task, TaskRequirementOption +from .tournaments import get_tournament + + +async def get_task(task_id: int, session: SessionDep) -> Task: + statement = select(Task).where(Task.id == task_id).options(selectinload(Task.criteria)) + task = (await session.execute(statement)).scalar() + if not task: + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Task not found!") + return task + + +async def get_task_by_tournament( + tournament_id: int, task_id: int, session: SessionDep +) -> Task: + tournament = await get_tournament(tournament_id, session) + task = await get_task(task_id, session) + if task.tournament_id != tournament.id: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, detail="Task does not belong to this tournament" + ) + return task + + +async def get_requirements( + requirement_names: list[str], session: SessionDep +) -> list[TaskRequirementOption]: + + if not requirement_names: + return [] + + stmt = select(TaskRequirementOption).where( + TaskRequirementOption.name.in_(requirement_names) + ) + result = await session.execute(stmt) + req_options = result.scalars().all() + + if len(req_options) != len(requirement_names): + found_names = {opt.name for opt in req_options} + missing = set(requirement_names) - found_names + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Requirement options not found: {', '.join(missing)}", + ) + + return list(req_options) diff --git a/backend/app/utils/tournaments.py b/backend/app/utils/tournaments.py new file mode 100644 index 0000000..11076f5 --- /dev/null +++ b/backend/app/utils/tournaments.py @@ -0,0 +1,30 @@ +from fastapi import HTTPException, status +from sqlalchemy import select +from sqlalchemy.orm import selectinload +from app.dependencies.session import SessionDep +from app.models import Tournament, Team, User +from .fsm import auto_update_tournament_status + +tournament_load_options = ( + selectinload(Tournament.status), + selectinload(Tournament.creator).selectinload(User.roles), + selectinload(Tournament.creator).selectinload(User.evaluates_in), + selectinload(Tournament.tasks), + selectinload(Tournament.teams).selectinload(Team.members), + selectinload(Tournament.juries).selectinload(User.evaluates_in), +) + + +async def get_tournament(tournament_id: int, session: SessionDep) -> Tournament: + statement = ( + select(Tournament) + .where(Tournament.id == tournament_id) + .options(*tournament_load_options) + ) + tournament = (await session.execute(statement)).scalar_one_or_none() + if not tournament: + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Tournament not found!") + + await auto_update_tournament_status(tournament, session) + + return tournament diff --git a/backend/app/utils/users.py b/backend/app/utils/users.py new file mode 100644 index 0000000..2f2bb27 --- /dev/null +++ b/backend/app/utils/users.py @@ -0,0 +1,31 @@ +from fastapi import HTTPException, status +from sqlalchemy import select +from app.dependencies.session import SessionDep +from app.models import User + + +async def get_user_by_email(email: str, session: SessionDep): + statement = select(User).where(User.email == email) + + user = (await session.execute(statement)).scalar() + if not user: + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found!") + return user + + +async def get_user_by_firebase_uid(firebase_uid: str, session: SessionDep): + statement = select(User).where(User.firebase_uid == firebase_uid) + + user = (await session.execute(statement)).scalar() + if not user: + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found!") + return user + + +async def get_user_by_id(id: int, session: SessionDep): + statement = select(User).where(User.id == id) + + user = (await session.execute(statement)).scalar() + if not user: + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found!") + return user diff --git a/backend/app/websockets.py b/backend/app/websockets.py new file mode 100644 index 0000000..4977221 --- /dev/null +++ b/backend/app/websockets.py @@ -0,0 +1,21 @@ +import socketio +from app.config import settings +from app.dependencies import get_current_user, get_session +from app import app + +sio = socketio.AsyncServer(cors_allowed_origins=settings.CORS_ORIGINS, async_mode="asgi") + + +@sio.event +async def connect(sid, environ, auth: str): + async for session in get_session(): + user = await get_current_user(session, auth["token"]) + app.state.user_websocket_sessions[user.id] = {"sid": sid} + + +@sio.event +async def disconnect(sid): + for k, v in app.state.user_websocket_sessions.items(): + if v["sid"] == sid: + app.state.user_websocket_sessions.pop(k) + return diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..a26b58d --- /dev/null +++ b/backend/main.py @@ -0,0 +1,4 @@ +import uvicorn + +if __name__ == "__main__": + uvicorn.run("app:socket_app", host="127.0.0.1", port=8000, reload=True) diff --git a/backend/pyproject.toml b/backend/pyproject.toml new file mode 100644 index 0000000..9c3119a --- /dev/null +++ b/backend/pyproject.toml @@ -0,0 +1,6 @@ +[tool.ruff] +line-length = 95 +exclude = ['alembic'] + +[tool.ruff.lint] +extend-select = ["E501"] diff --git a/backend/pytest.ini b/backend/pytest.ini new file mode 100644 index 0000000..75d353f --- /dev/null +++ b/backend/pytest.ini @@ -0,0 +1,7 @@ +[pytest] + +addopts = -v +asyncio_mode = auto +testpaths = tests + +python_files = tests.py test_*.py *_test.py \ No newline at end of file diff --git a/backend/tests/__init__.py b/backend/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py new file mode 100644 index 0000000..f9c64b7 --- /dev/null +++ b/backend/tests/conftest.py @@ -0,0 +1,54 @@ +import pytest +from httpx import AsyncClient, ASGITransport +from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker + +from app.models import Base +from app import app +from app.db import get_session + + +TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:" + +engine = create_async_engine( + TEST_DATABASE_URL, +) + +AsyncTestingSessionLocal = async_sessionmaker(bind=engine, expire_on_commit=False) + + +@pytest.fixture(autouse=True) +async def setup_database(): + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + yield + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.drop_all) + + +@pytest.fixture() +async def db_session(): + async with AsyncTestingSessionLocal() as session: + yield session + + +@pytest.fixture +async def create(db_session): + async def _create(factory_class, **kwargs): + obj = factory_class.build(**kwargs) + db_session.add(obj) + await db_session.flush() + return obj + + return _create + + +@pytest.fixture +async def client(db_session): + async def override_get_session(): + yield db_session + + app.dependency_overrides[get_session] = override_get_session + transport = ASGITransport(app=app) + async with AsyncClient(transport=transport, base_url="http://test") as ac: + yield ac + app.dependency_overrides.clear() diff --git a/backend/tests/factories.py b/backend/tests/factories.py new file mode 100644 index 0000000..f247b86 --- /dev/null +++ b/backend/tests/factories.py @@ -0,0 +1,325 @@ +import factory +import factory.fuzzy +from factory.faker import Faker +from factory.alchemy import SQLAlchemyModelFactory + +from app.config import settings +from app.models import ( + User, + Role, + News, + NewsCattegory, + TeamMember, + Team, + Tournament, + TournamentStatusOption, + Task, + TaskStatusOption, + TaskRequirementOption, + TaskRequirementCategory, + Submission, + SubmissionUrl, + SubmissionUrlOption, + JuryAssignment, + JuryAssignmentStatusOption, + SubmissionEvaluation, + Notification, + RoleRequest, + TaskEvaluationCriterion, + CriterionScore, +) +import datetime + + +class BaseFactory(SQLAlchemyModelFactory): + class Meta: + abstract = True + sqlalchemy_session = None + sqlalchemy_session_persistence = None + + +class BaseOptionFactory(BaseFactory): + class Meta: + abstract = True + sqlalchemy_get_or_create = ('name',) + + name = Faker("name") + display_name = factory.LazyAttribute(lambda f: f.name.upper()) + + +class BaseDatetimeFactory(BaseFactory): + class Meta: + abstract = True + + created_at = factory.Faker("date_time") + updated_at = factory.LazyAttribute(lambda o: o.created_at + datetime.timedelta(hours=1)) + + +class UserFactory(BaseFactory): + class Meta: + model = User + + firebase_uid = Faker("uuid4") + full_name = Faker("name") + email = factory.Sequence(lambda n: f"user{n}@example.com") + + +class RoleFactory(BaseFactory): + class Meta: + model = Role + + name = factory.Iterator([option["name"] for option in settings.ROLE_OPTIONS]) + display_name = factory.LazyAttribute( + lambda role: next( + option["display_name"] + for option in settings.ROLE_OPTIONS + if option["name"] == role.name + ) + ) + description = factory.LazyAttribute( + lambda role: next( + option["description"] + for option in settings.ROLE_OPTIONS + if option["name"] == role.name + ) + ) + + +class TournamentStatusOptionFactory(BaseOptionFactory): + class Meta: + model = TournamentStatusOption + sqlalchemy_get_or_create = ('name',) + + name = factory.Iterator(list(settings.TOURNAMENT_STATUS_NAMES.__dict__.values())) + + +class NewsCattegoryFactory(BaseOptionFactory): + class Meta: + model = NewsCattegory + + name = factory.Iterator([option["name"] for option in settings.NEWS_CATEGORY_OPTIONS]) + display_name = factory.LazyAttribute( + lambda category: next( + option["display_name"] + for option in settings.NEWS_CATEGORY_OPTIONS + if option["name"] == category.name + ) + ) + + +class NewsFactory(BaseFactory): + class Meta: + model = News + + title = factory.Sequence(lambda n: f"news_{n} title") + excerpt = factory.Sequence(lambda n: f"news_{n} excerpt") + body = factory.Sequence(lambda n: f"news_{n} body") + category = factory.SubFactory(NewsCattegoryFactory) + is_important = False + + +class TournamentFactory(BaseFactory): + class Meta: + model = Tournament + + title = Faker("catch_phrase") + description = Faker("paragraph") + start_date = factory.LazyAttribute( + lambda o: o.reg_end + datetime.timedelta(days=factory.fuzzy.FuzzyInteger(1, 10).fuzz()) + ) + reg_start = Faker("future_datetime", end_date="+30d") + reg_end = factory.LazyAttribute( + lambda o: ( + o.reg_start + datetime.timedelta(days=factory.fuzzy.FuzzyInteger(1, 10).fuzz()) + ) + ) + max_teams = Faker("pyint", min_value=10, max_value=100) + min_people_in_team = Faker("pyint", min_value=1, max_value=100) + max_people_in_team = Faker("pyint", min_value=1, max_value=100) + + creator = factory.SubFactory(UserFactory) + status = factory.SubFactory(TournamentStatusOptionFactory) + + +class TeamFactory(BaseFactory): + class Meta: + model = Team + + name = Faker("name") + team_email = Faker("email") + contact_info = Faker("numerify", text="+38050#######") + + tournament = factory.SubFactory(TournamentFactory) + captain = None + + @classmethod + def with_captain(cls, **kwargs): + team = cls.build(**kwargs) + captain = TeamMemberFactory.build(team=team) + team.captain = captain + return team, captain + + +class TeamMemberFactory(BaseFactory): + class Meta: + model = TeamMember + + full_name = Faker("name") + email = Faker("email") + telegram = factory.Sequence(lambda n: f"@user_{n}") + educational_institution = Faker("company") + team = factory.SubFactory(TeamFactory) + tournament = factory.SubFactory(TournamentFactory) + + +class TaskStatusOptionFactory(BaseOptionFactory): + class Meta: + model = TaskStatusOption + + name = factory.Iterator([option["name"] for option in settings.TASK_STATUS_OPTIONS]) + display_name = factory.LazyAttribute( + lambda status: next( + option["display_name"] + for option in settings.TASK_STATUS_OPTIONS + if option["name"] == status.name + ) + ) + + +class JuryAssignmentStatusOptionFactory(BaseOptionFactory): + class Meta: + model = JuryAssignmentStatusOption + + name = factory.Iterator( + [option["name"] for option in settings.JURY_ASSIGNMENT_STATUS_OPTIONS] + ) + display_name = factory.LazyAttribute( + lambda status: next( + option["display_name"] + for option in settings.JURY_ASSIGNMENT_STATUS_OPTIONS + if option["name"] == status.name + ) + ) + + +class TaskFactory(BaseFactory): + class Meta: + model = Task + + title = Faker("catch_phrase") + description = Faker("paragraph") + start_time = Faker("future_datetime") + end_time = Faker("future_datetime") + tournament = factory.SubFactory(TournamentFactory) + status_id = factory.Iterator([option["name"] for option in settings.TASK_STATUS_OPTIONS]) + min_reviews_per_submission = 2 + max_score = 10 + is_leaderboard_visible = True + + @classmethod + def _adjust_kwargs(cls, **kwargs): + status_id = kwargs.get("status_id") + if isinstance(status_id, TaskStatusOption): + kwargs["status"] = status_id + kwargs.pop("status_id") + return super()._adjust_kwargs(**kwargs) + + +class TaskRequirementCategoryFactory(BaseOptionFactory): + class Meta: + model = TaskRequirementCategory + + +class TaskRequirementOptionFactory(BaseOptionFactory): + class Meta: + model = TaskRequirementOption + + category = factory.SubFactory(TaskRequirementCategoryFactory) + + +class SubmissionFactory(BaseFactory): + class Meta: + model = Submission + + team = factory.SubFactory(TeamFactory) + task = factory.LazyAttribute(lambda obj: TaskFactory.build(tournament=obj.team.tournament)) + + +class SubmissionUrlOptionFactory(BaseOptionFactory): + class Meta: + model = SubmissionUrlOption + + name = factory.Sequence(lambda n: f"url_option_{n}") + + +class SubmissionUrlFactory(BaseFactory): + class Meta: + model = SubmissionUrl + + submission = factory.SubFactory(SubmissionFactory) + url = factory.SubFactory(SubmissionUrlOptionFactory) + value = factory.Sequence(lambda n: f"https://example.com/submission/{n}") + + +class TaskEvaluationCriterionFactory(BaseFactory): + class Meta: + model = TaskEvaluationCriterion + + task = factory.SubFactory(TaskFactory) + name = factory.Sequence(lambda n: f"Criterion {n}") + description = None + weight = 1 + max_score = 10 + + +class JuryAssignmentFactory(BaseDatetimeFactory): + class Meta: + model = JuryAssignment + + submission = factory.SubFactory(SubmissionFactory) + task = factory.SelfAttribute("submission.task") + jury = factory.SubFactory(UserFactory) + status = factory.SubFactory(JuryAssignmentStatusOptionFactory) + + @classmethod + def _adjust_kwargs(cls, **kwargs): + status_id = kwargs.get("status_id") + if isinstance(status_id, JuryAssignmentStatusOption): + kwargs["status"] = status_id + kwargs.pop("status_id") + return super()._adjust_kwargs(**kwargs) + + +class SubmissionEvaluationFactory(BaseFactory): + class Meta: + model = SubmissionEvaluation + + assignment = factory.SubFactory(JuryAssignmentFactory) + submission = factory.SelfAttribute("assignment.submission") + jury = factory.SelfAttribute("assignment.jury") + comment = None + + +class CriterionScoreFactory(BaseFactory): + class Meta: + model = CriterionScore + + evaluation = factory.SubFactory(SubmissionEvaluationFactory) + criterion = factory.SubFactory(TaskEvaluationCriterionFactory) + score = factory.Faker("pyint", min_value=0, max_value=100) + + +class NotificationFactory(BaseFactory): + class Meta: + model = Notification + + body = factory.Sequence(lambda n: f"notification_{n}") + user = factory.SubFactory(UserFactory) + + +class RoleRequestFactory(BaseFactory): + class Meta: + model = RoleRequest + + role = factory.SubFactory(RoleFactory) + user = factory.SubFactory(UserFactory) diff --git a/backend/tests/routes/__init__.py b/backend/tests/routes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/tests/routes/test_jury.py b/backend/tests/routes/test_jury.py new file mode 100644 index 0000000..c73152b --- /dev/null +++ b/backend/tests/routes/test_jury.py @@ -0,0 +1,266 @@ +import pytest +from app import app +from app.dependencies import get_current_user +from app.models import TaskEvaluationCriterion +from tests.factories import ( + JuryAssignmentStatusOptionFactory, + RoleFactory, + SubmissionUrlOptionFactory, + TaskFactory, + TaskStatusOptionFactory, + TeamFactory, + TeamMemberFactory, + TournamentFactory, + TournamentStatusOptionFactory, + UserFactory, + JuryAssignmentFactory, + JuryAssignmentStatusOptionFactory, + SubmissionFactory, + SubmissionEvaluationFactory, + TaskEvaluationCriterionFactory, + CriterionScoreFactory +) +from app.config import settings + +async def test_jury_tasks(create, client, db_session): + closed = await create(TaskStatusOptionFactory, name=settings.TASK_STATUS_NAMES.SUBMISSION_CLOSED) + jury = await create(UserFactory) + tournament = await create(TournamentFactory) + t1 = await create(TaskFactory, status=closed, tournament=tournament) + t2 = await create(TaskFactory, status=closed, tournament=tournament) + captain1 = await create(TeamMemberFactory, tournament=tournament) + captain2 = await create(TeamMemberFactory, tournament=tournament) + team1 = await create(TeamFactory, tournament=tournament, captain=captain1) + team2 = await create(TeamFactory, tournament=tournament, captain=captain2) + s1 = await create(SubmissionFactory, task=t1, team=team1) + s2 = await create(SubmissionFactory, task=t2, team=team2) + await create(JuryAssignmentFactory, jury=jury, task=t1, + submission=s1) + await create(JuryAssignmentFactory, jury=jury, task=t2, + submission=s2) + app.dependency_overrides[get_current_user] = lambda: jury + + resp = await client.get('/jury/tasks/') + assert resp.status_code == 200 + assert len(resp.json()) == 2 + t_ids = [t['id'] for t in resp.json()] + assert t1.id in t_ids + assert t2.id in t_ids + + app.dependency_overrides.pop(get_current_user) + +async def test_jury_tasks_draft(create, client, db_session): + draft = await create(TaskStatusOptionFactory, name=settings.TASK_STATUS_NAMES.DRAFT) + jury = await create(UserFactory) + tournament = await create(TournamentFactory) + t = await create(TaskFactory, status=draft, tournament=tournament) + captain = await create(TeamMemberFactory, tournament=tournament) + team = await create(TeamFactory, tournament=tournament, captain=captain) + s = await create(SubmissionFactory, task=t, team=team) + await create(JuryAssignmentFactory, jury=jury, task=t, + submission=s) + app.dependency_overrides[get_current_user] = lambda: jury + + resp = await client.get('/jury/tasks/') + assert resp.status_code == 200 + assert len(resp.json()) == 0 + + app.dependency_overrides.pop(get_current_user) + +async def test_jury_assignments(create, client, db_session): + closed = await create(TaskStatusOptionFactory, name=settings.TASK_STATUS_NAMES.SUBMISSION_CLOSED) + jury = await create(UserFactory) + tournament = await create(TournamentFactory) + t1 = await create(TaskFactory, status=closed, tournament=tournament) + captain1 = await create(TeamMemberFactory, tournament=tournament) + captain2 = await create(TeamMemberFactory, tournament=tournament) + team1 = await create(TeamFactory, tournament=tournament, captain=captain1) + team2 = await create(TeamFactory, tournament=tournament, captain=captain2) + s1 = await create(SubmissionFactory, task=t1, team=team1) + s2 = await create(SubmissionFactory, task=t1, team=team2) + assigned = await create(JuryAssignmentStatusOptionFactory, name=settings.JURY_ASSIGNMENT_STATUS_NAMES.ASSIGNED) + await create(JuryAssignmentFactory, jury=jury, task=t1, + submission=s1, status=assigned) + await create(JuryAssignmentFactory, jury=jury, task=t1, + submission=s2, status=assigned) + app.dependency_overrides[get_current_user] = lambda: jury + + resp = await client.get(f'/jury/tasks/{t1.id}/assignments/') + assert resp.status_code == 200 + assert len(resp.json()) == 2 + t_ids = [t['task_id'] for t in resp.json()] + assert t_ids[0] == t1.id + assert t_ids[1] == t1.id + + app.dependency_overrides.pop(get_current_user) + +async def test_jury_assignment_draft(create, client, db_session): + jury = await create(UserFactory) + a = await create(JuryAssignmentFactory, jury=jury) + app.dependency_overrides[get_current_user] = lambda: jury + + resp = await client.get(f'/jury/tasks/{a.task.id}/assignments/') + assert resp.status_code == 400 + assert resp.json()['detail'] == 'The submissions are still open to be received!' + + + app.dependency_overrides.pop(get_current_user) + +async def test_update_evaluation(create, client, db_session): + jury = await create(UserFactory) + t = await create(TaskFactory) + cr1 = await create(TaskEvaluationCriterionFactory, task=t) + cr2 = await create(TaskEvaluationCriterionFactory, task=t) + s = await create(SubmissionFactory) + a = await create(JuryAssignmentFactory, jury=jury, task=t) + e = await create(SubmissionEvaluationFactory, assignment=a, submission=s) + + c1 = await create(CriterionScoreFactory, evaluation=e, criterion=cr1) + c2 = await create(CriterionScoreFactory, evaluation=e, criterion=cr2) + await db_session.refresh(e, ['criterion_scores']) + comment = e.comment + criteria = e.criterion_scores + + app.dependency_overrides[get_current_user] = lambda: jury + + resp = await client.patch(f'/jury/assignments/{a.id}/evaluation/', json={ + 'comment': 'A comment', + 'criterion_scores': [ + {'criterion_id': c1.id, 'score': 7}, + {'criterion_id': c2.id, 'score': 5} + ] + }) + assert resp.status_code == 200 + assert resp.json()['comment'] == 'A comment' + assert resp.json()['comment'] != comment + assert len(resp.json()['criterion_scores']) == 2 + assert len(resp.json()['criterion_scores']) != criteria + + + app.dependency_overrides.pop(get_current_user) + +async def test_generate_assignments_no_submissions(create, client, db_session): + organizer = await create(RoleFactory, name=settings.ROLE_NAMES.ORGANIZER) + creator = await create(UserFactory, roles=[organizer]) + tournament = await create(TournamentFactory, creator=creator) + await db_session.refresh(tournament, ['juries']) + closed = await create(TaskStatusOptionFactory, name=settings.TASK_STATUS_NAMES.SUBMISSION_CLOSED) + task = await create(TaskFactory, tournament=tournament, status=closed) + captain = await create(TeamMemberFactory, tournament=tournament) + team = await create(TeamFactory, captain=captain, tournament=tournament) + await create(SubmissionFactory, task=task, team=team) + + app.dependency_overrides[get_current_user] = lambda: creator + + resp = await client.post(f'/tournaments/{tournament.id}/tasks/{task.id}/assignments/generate/') + assert resp.status_code == 400 + assert resp.json()['detail'] == 'Not enough juries to satisfy minimum reviews per submission' + + app.dependency_overrides.pop(get_current_user) + +async def test_generate_assignments_already_generated(create, client, db_session): + organizer = await create(RoleFactory, name=settings.ROLE_NAMES.ORGANIZER) + creator = await create(UserFactory, roles=[organizer]) + tournament = await create(TournamentFactory, creator=creator) + await db_session.refresh(tournament, ['juries']) + closed = await create(TaskStatusOptionFactory, name=settings.TASK_STATUS_NAMES.SUBMISSION_CLOSED) + task = await create(TaskFactory, tournament=tournament, status=closed, min_reviews_per_submission=0) + captain = await create(TeamMemberFactory, tournament=tournament) + team = await create(TeamFactory, captain=captain, tournament=tournament) + submission = await create(SubmissionFactory, task=task, team=team) + await create(JuryAssignmentFactory, task=task, submission=submission) + + app.dependency_overrides[get_current_user] = lambda: creator + + resp = await client.post(f'/tournaments/{tournament.id}/tasks/{task.id}/assignments/generate/') + assert resp.status_code == 400 + assert resp.json()['detail'] == 'Assignments already generated for this task' + + app.dependency_overrides.pop(get_current_user) + +async def test_generate_assignments_no_juries(create, client, db_session): + organizer = await create(RoleFactory, name=settings.ROLE_NAMES.ORGANIZER) + creator = await create(UserFactory, roles=[organizer]) + tournament = await create(TournamentFactory, creator=creator, juries=[]) + await db_session.refresh(tournament, ['juries']) + closed = await create(TaskStatusOptionFactory, name=settings.TASK_STATUS_NAMES.SUBMISSION_CLOSED) + task = await create(TaskFactory, tournament=tournament, status=closed) + captain = await create(TeamMemberFactory, tournament=tournament) + team = await create(TeamFactory, captain=captain, tournament=tournament) + await create(SubmissionFactory, task=task, team=team) + app.dependency_overrides[get_current_user] = lambda: creator + + resp = await client.post(f'/tournaments/{tournament.id}/tasks/{task.id}/assignments/generate/') + assert resp.status_code == 400 + assert resp.json()['detail'] == 'Not enough juries to satisfy minimum reviews per submission' + + app.dependency_overrides.pop(get_current_user) + +# Integration + +async def test_generate_assignments_and_submit_evaluation(create, client, db_session): + admin_role = await create(RoleFactory, name=settings.ROLE_NAMES.ADMIN) + organizer = await create(UserFactory, roles=[admin_role]) + jury1 = await create(UserFactory) + jury2 = await create(UserFactory) + participant = await create(UserFactory) + + tournament_status = await create( + TournamentStatusOptionFactory, name=settings.TOURNAMENT_STATUS_NAMES.DRAFT + ) + await create(TaskStatusOptionFactory, name=settings.TASK_STATUS_NAMES.DRAFT) + await create( + JuryAssignmentStatusOptionFactory, name=settings.JURY_ASSIGNMENT_STATUS_NAMES.ASSIGNED + ) + closed = await create(TaskStatusOptionFactory, name=settings.TASK_STATUS_NAMES.SUBMISSION_CLOSED) + tournament = await create(TournamentFactory, creator=organizer, status=tournament_status) + await db_session.refresh(tournament, ["juries"]) + tournament.juries.extend([jury1, jury2]) + + task = await create(TaskFactory, tournament=tournament, min_reviews_per_submission=2, status=closed) + criterion = TaskEvaluationCriterion( + task_id=task.id, + name="Completeness", + weight=1, + max_score=10, + ) + db_session.add(criterion) + + team = await create(TeamFactory, tournament=tournament) + await create(TeamMemberFactory, team=team, tournament=tournament, email=participant.email) + option = await create(SubmissionUrlOptionFactory, name="github") + await db_session.commit() + + app.dependency_overrides[get_current_user] = lambda: participant + submission_resp = await client.post( + f"/tournaments/{tournament.id}/tasks/{task.id}/submissions/", + json={ + "team_id": team.id, + "urls": [{"url_id": option.name, "value": "https://github.com/example/repo"}], + }, + ) + assert submission_resp.status_code == 201 + + app.dependency_overrides[get_current_user] = lambda: organizer + generate_resp = await client.post( + f"/tournaments/{tournament.id}/tasks/{task.id}/assignments/generate/" + ) + assert generate_resp.status_code == 200 + assert len(generate_resp.json()) == 2 + + app.dependency_overrides[get_current_user] = lambda: jury1 + assignments_resp = await client.get(f"/jury/tasks/{task.id}/assignments/") + assert assignments_resp.status_code == 200 + assignment = assignments_resp.json()[0] + + evaluation_resp = await client.post( + f"/jury/assignments/{assignment['id']}/evaluation/", + json={ + "comment": "Solid work", + "criterion_scores": [{"criterion_id": criterion.id, "score": 8}], + }, + ) + assert evaluation_resp.status_code == 201 + assert evaluation_resp.json()["comment"] == "Solid work" + + app.dependency_overrides.pop(get_current_user) diff --git a/backend/tests/routes/test_profile.py b/backend/tests/routes/test_profile.py new file mode 100644 index 0000000..06646bc --- /dev/null +++ b/backend/tests/routes/test_profile.py @@ -0,0 +1,32 @@ +import pytest +from sqlalchemy import select +from sqlalchemy.orm import selectinload +from app import app +from app.dependencies import get_current_user +from app.models import User +from tests.factories import UserFactory + + +async def test_update_profile(create, client, db_session): + user = await create(UserFactory) + stmt = select(User).where(User.id == user.id).options(selectinload(User.roles)) + user = (await db_session.execute(stmt)).unique().scalar_one() + full_name = "John Doe" + app.dependency_overrides[get_current_user] = lambda: user + assert user.full_name != full_name + resp = await client.patch("/profile/", json={"full_name": full_name}) + assert resp.json()["full_name"] == full_name + app.dependency_overrides.pop(get_current_user) + + +async def test_delete_profile(create, client, db_session, mocker): + user = await create(UserFactory) + stmt = select(User).where(User.id == user.id).options(selectinload(User.roles)) + user = (await db_session.execute(stmt)).unique().scalar_one() + assert await db_session.get(User, user.id) is not None + mocker.patch("app.routes.profile.auth.get_user", return_value=False) + app.dependency_overrides[get_current_user] = lambda: user + resp = await client.delete("/profile/") + assert resp.status_code == 204 + assert await db_session.get(User, user.id) is None + app.dependency_overrides.pop(get_current_user) diff --git a/backend/tests/routes/test_role_request.py b/backend/tests/routes/test_role_request.py new file mode 100644 index 0000000..7d51649 --- /dev/null +++ b/backend/tests/routes/test_role_request.py @@ -0,0 +1,126 @@ +import pytest +from sqlalchemy import select +from sqlalchemy.orm import selectinload +from app import app +from app.dependencies import get_current_user +from app.dependencies.admin_user import get_admin_user +from app.models import RoleRequest, User +from tests.factories import UserFactory, RoleFactory +from app.websockets import sio + + +@pytest.mark.asyncio +async def test_role_request_and_approval(create, client, db_session, mocker): + user = await create(UserFactory) + stmt = select(User).where(User.id == user.id).options(selectinload(User.roles)) + user = (await db_session.execute(stmt)).unique().scalar_one() + role = await create(RoleFactory) + app.dependency_overrides[get_current_user] = lambda: user + app.dependency_overrides[get_admin_user] = lambda: user + assert len(user.roles) == 0 + resp = await client.post( + "/role-requests/", + json={ + "role_name": role.name, + "user_id": user.id, + }, + ) + assert resp.status_code == 201 + assert resp.json()["role_name"] == role.name + assert resp.json()["user_id"] == user.id + req_id = resp.json()["id"] + s = select(RoleRequest) + assert len((await db_session.execute(s)).scalars().all()) == 1 + app.state.user_websocket_sessions[user.id] = {"sid": "test"} + spy = mocker.spy(sio, "emit") + resp = await client.post(f"/role-requests/{req_id}/approve/") + assert resp.status_code == 200 + assert len(resp.json()["roles"]) == 1 + assert len((await db_session.execute(s)).scalars().all()) == 0 + assert spy.call_count == 1 + app.dependency_overrides.pop(get_current_user) + app.dependency_overrides.pop(get_admin_user) + + +@pytest.mark.asyncio +async def test_create_role_request_exists(create, client, db_session): + user = await create(UserFactory) + stmt = select(User).where(User.id == user.id).options(selectinload(User.roles)) + user = (await db_session.execute(stmt)).unique().scalar_one() + role = await create(RoleFactory) + app.dependency_overrides[get_current_user] = lambda: user + r = RoleRequest(role_name=role.name, user_id=user.id) + db_session.add(r) + await db_session.commit() + resp = await client.post( + "/role-requests/", + json={ + "role_name": role.name, + "user_id": user.id, + }, + ) + assert resp.status_code == 400 + assert "exists" in resp.json()["detail"] + assert len(user.roles) == 0 + s = select(RoleRequest) + assert len((await db_session.execute(s)).scalars().all()) == 1 + app.dependency_overrides.pop(get_current_user) + + +@pytest.mark.asyncio +async def test_role_request_disapproval(create, client, db_session, mocker): + user = await create(UserFactory) + stmt = select(User).where(User.id == user.id).options(selectinload(User.roles)) + user = (await db_session.execute(stmt)).unique().scalar_one() + role = await create(RoleFactory) + app.dependency_overrides[get_current_user] = lambda: user + app.dependency_overrides[get_admin_user] = lambda: user + r = RoleRequest(role_name=role.name, user_id=user.id) + db_session.add(r) + await db_session.commit() + await db_session.refresh(r) + app.state.user_websocket_sessions[user.id] = {"sid": "test"} + spy = mocker.spy(sio, "emit") + resp = await client.post(f"/role-requests/{r.id}/reject/") + assert resp.status_code == 200 + assert len(user.roles) == 0 + assert spy.call_count == 1 + s = select(RoleRequest) + assert len((await db_session.execute(s)).scalars().all()) == 0 + app.dependency_overrides.pop(get_current_user) + app.dependency_overrides.pop(get_admin_user) + + +@pytest.mark.asyncio +async def test_get_role_request(create, client, db_session): + user = await create(UserFactory) + stmt = select(User).where(User.id == user.id).options(selectinload(User.roles)) + user = (await db_session.execute(stmt)).unique().scalar_one() + role = await create(RoleFactory) + app.dependency_overrides[get_current_user] = lambda: user + r = RoleRequest(role_name=role.name, user_id=user.id) + db_session.add(r) + await db_session.commit() + await db_session.refresh(r) + resp = await client.get(f"/role-requests/{r.id}/") + assert resp.status_code == 200 + assert resp.json()["role_name"] == r.role_name + assert resp.json()["user_id"] == r.user_id + app.dependency_overrides.pop(get_current_user) + + +@pytest.mark.asyncio +async def test_delete_role_request(create, client, db_session): + user = await create(UserFactory) + stmt = select(User).where(User.id == user.id).options(selectinload(User.roles)) + user = (await db_session.execute(stmt)).unique().scalar_one() + role = await create(RoleFactory) + app.dependency_overrides[get_current_user] = lambda: user + r = RoleRequest(role_name=role.name, user_id=user.id) + db_session.add(r) + await db_session.commit() + await db_session.refresh(r) + resp = await client.delete(f"/role-requests/{r.id}/") + assert resp.status_code == 204 + assert not (await db_session.execute(select(RoleRequest))).scalar() + app.dependency_overrides.pop(get_current_user) diff --git a/backend/tests/routes/test_tournaments.py b/backend/tests/routes/test_tournaments.py new file mode 100644 index 0000000..94fbd43 --- /dev/null +++ b/backend/tests/routes/test_tournaments.py @@ -0,0 +1,60 @@ +import pytest +from app import app +from app.dependencies import get_current_user +from tests.factories import ( + TournamentFactory, + UserFactory, + TournamentStatusOptionFactory, + RoleFactory, +) +from app.config import settings + + +async def test_create_tournament(create, client, db_session): + adm = await create(RoleFactory, name=settings.ROLE_NAMES.ADMIN) + user = await create(UserFactory, roles=[adm]) + u1 = await create(UserFactory) + u2 = await create(UserFactory) + await create(TournamentStatusOptionFactory, name=settings.TOURNAMENT_STATUS_NAMES.DRAFT) + t = TournamentFactory.build() + app.dependency_overrides[get_current_user] = lambda: user + resp = await client.post( + f"/tournaments/", + json={ + "title": t.title, + "description": t.description, + "start_date": str(t.start_date), + "reg_start": str(t.reg_start), + "reg_end": str(t.reg_end), + "min_people_in_team": t.min_people_in_team, + "max_people_in_team": t.max_people_in_team, + "max_teams": t.max_teams, + "juries": [u1.id, u2.id], + }, + ) + assert resp.status_code == 201 + juries_ids = [jury["id"] for jury in resp.json()["juries"]] + assert u1.id in juries_ids + assert u2.id in juries_ids + assert len(resp.json()["juries"]) == 2 + app.dependency_overrides.pop(get_current_user) + + +async def test_update_tournament_juries(create, client): + user = await create(UserFactory) + u1 = await create(UserFactory) + u2 = await create(UserFactory) + t = await create(TournamentFactory, creator=user) + app.dependency_overrides[get_current_user] = lambda: user + resp = await client.patch( + f"/tournaments/{t.id}/", + json={ + "juries": [u1.id, u2.id], + }, + ) + assert resp.status_code == 200 + juries_ids = [jury["id"] for jury in resp.json()["juries"]] + assert u1.id in juries_ids + assert u2.id in juries_ids + assert len(resp.json()["juries"]) == 2 + app.dependency_overrides.pop(get_current_user) diff --git a/backend/tests/test_models.py b/backend/tests/test_models.py new file mode 100644 index 0000000..09630db --- /dev/null +++ b/backend/tests/test_models.py @@ -0,0 +1,717 @@ +import pytest +import asyncio +from datetime import datetime, timedelta +from sqlalchemy import select +from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import selectinload +from app.config import settings +from app.models import ( + User, + Role, + NewsCattegory, + News, + TeamMember, + Team, + Task, + Tournament, + TournamentStatusOption, + TaskRequirementCategory, + Submission, + SubmissionUrl, + SubmissionUrlOption, + SubmissionEvaluation, + CriterionScore, + Notification, + RoleRequest, +) + +from .factories import ( + UserFactory, + RoleFactory, + NewsFactory, + NewsCattegoryFactory, + TournamentStatusOptionFactory, + TournamentFactory, + TeamFactory, + TeamMemberFactory, + TaskStatusOptionFactory, + TaskFactory, + TaskRequirementCategoryFactory, + TaskRequirementOptionFactory, + SubmissionFactory, + SubmissionUrlFactory, + SubmissionUrlOptionFactory, + TaskEvaluationCriterionFactory, + SubmissionEvaluationFactory, + CriterionScoreFactory, + NotificationFactory, + RoleRequestFactory, +) + + +# USER TESTS +async def test_create_user(create): + user = await create(UserFactory) + assert user.id is not None + assert user.full_name is not None + assert user.email is not None + assert user.created_at is not None + + +async def test_create_user_duplicate_email(db_session, create): + user1 = await create(UserFactory) + + duplicate_user = UserFactory.build(email=user1.email) + db_session.add(duplicate_user) + + with pytest.raises(IntegrityError): + await db_session.flush() + + await db_session.rollback() + + +# ROLE TESTS +async def test_create_role(create): + role = await create(RoleFactory, name=settings.ROLE_NAMES.ORGANIZER) + assert role.name is not None + assert role.name == settings.ROLE_NAMES.ORGANIZER + + +async def test_role_name_unique_constraint(create): + await create(RoleFactory, name=settings.ROLE_NAMES.ORGANIZER) + + with pytest.raises(IntegrityError): + await create(RoleFactory, name=settings.ROLE_NAMES.ORGANIZER) + + +async def test_user_roles_relationship(db_session, create): + user = await create(UserFactory) + role_organizer = await create(RoleFactory, name=settings.ROLE_NAMES.ORGANIZER) + role_admin = await create(RoleFactory, name="admin") + + stmt = select(User).where(User.id == user.id).options(selectinload(User.roles)) + result = await db_session.execute(stmt) + user = result.unique().scalar_one() + + user.roles.extend([role_organizer, role_admin]) + await db_session.commit() + + stmt_check = select(User).where(User.id == user.id).options(selectinload(User.roles)) + result_check = await db_session.execute(stmt_check) + db_user = result_check.unique().scalar_one() + + assert len(db_user.roles) == 2 + assert "admin" in [r.name for r in db_user.roles] + + +async def test_user_participates_in_relationship(db_session, create): + user = await create(UserFactory) + tournament1 = await create(TournamentFactory) + tournament2 = await create(TournamentFactory) + other_tournament = await create(TournamentFactory) + + team1 = await create(TeamFactory, tournament=tournament1) + team2 = await create(TeamFactory, tournament=tournament2) + other_team = await create(TeamFactory, tournament=other_tournament) + + await create(TeamMemberFactory, team=team1, tournament=tournament1, email=user.email) + await create(TeamMemberFactory, team=team2, tournament=tournament2, email=user.email) + await create(TeamMemberFactory, team=other_team, tournament=other_tournament) + + stmt = select(User).where(User.id == user.id).options(selectinload(User.participates_in)) + result = await db_session.execute(stmt) + db_user = result.unique().scalar_one() + + assert {tournament.id for tournament in db_user.participates_in} == { + tournament1.id, + tournament2.id, + } + + +# TEAM/TEAM MEMBER TESTS +async def test_create_team_member(create): + member = await create(TeamMemberFactory) + + assert member.id is not None + assert member.team_id == member.team.id + + +async def test_team_member_duplicate_email_same_team(create, db_session): + team = await create(TeamFactory) + member1 = await create(TeamMemberFactory, team=team) + + duplicate = TeamMemberFactory.build(email=member1.email, team=team) + db_session.add(duplicate) + + with pytest.raises(IntegrityError): + await db_session.flush() + + await db_session.rollback() + + +async def test_team_member_duplicate_email_different_tournaments(create, db_session): + team1 = await create(TeamFactory) + team2 = await create(TeamFactory) + member1 = await create(TeamMemberFactory, tournament=team1.tournament) + member2 = await create(TeamMemberFactory, email=member1.email, tournament=team2.tournament) + + await db_session.commit() + assert member1.email == member2.email + + +async def test_team_member_duplicate_email_same_tournament(create, db_session): + team1 = await create(TeamFactory) + team2 = await create(TeamFactory, tournament=team1.tournament) + member1 = await create(TeamMemberFactory, tournament=team1.tournament) + with pytest.raises(IntegrityError): + await create(TeamMemberFactory, email=member1.email, tournament=team2.tournament) + await db_session.flush() + + await db_session.rollback() + + +async def test_create_team(create): + team = await create(TeamFactory) + assert team.id is not None + assert team.name is not None + assert team.team_email is not None + + +async def test_team_duplicate_email(db_session, create): + email = "duplicate@example.com" + await create(TeamFactory, team_email=email) + + with pytest.raises(IntegrityError): + await create(TeamFactory, team_email=email) + + await db_session.rollback() + + +async def test_set_team_captain(db_session, create): + team = await create(TeamFactory) + member = await create(TeamMemberFactory, team=team) + team.captain_id = member.id + db_session.add(team) + await db_session.commit() + await db_session.refresh(team) + + assert team.captain_id == member.id + + +async def test_team_team_member_relationship(db_session, create): + team = await create(TeamFactory) + member = await create(TeamMemberFactory, team=team) + + stmt = select(Team).where(Team.id == team.id).options(selectinload(Team.members)) + result = await db_session.execute(stmt) + db_team = result.unique().scalar_one() + + assert len(db_team.members) == 1 + assert db_team.members[0].id == member.id + + +async def test_team_cascade_delete_members(db_session, create): + team = await create(TeamFactory) + member = await create(TeamMemberFactory, team=team) + member_id = member.id + + await db_session.delete(team) + await db_session.commit() + + stmt = select(TeamMember).where(TeamMember.id == member_id) + result = await db_session.execute(stmt) + assert result.scalar_one_or_none() is None + + +async def test_tournament_cascade_delete_children(db_session, create): + tournament = await create(TournamentFactory) + task = await create(TaskFactory, tournament=tournament) + team = await create(TeamFactory, tournament=tournament) + member = await create(TeamMemberFactory, team=team, tournament=tournament) + submission = await create(SubmissionFactory, team=team) + url = await create(SubmissionUrlFactory, submission=submission) + evaluation = await create(SubmissionEvaluationFactory, submission=submission) + criterion_score = await create(CriterionScoreFactory, evaluation=evaluation) + + ids = { + "task": task.id, + "team": team.id, + "member": member.id, + "submission": submission.team_id, + "url": (url.submission_id, url.url_id), + "evaluation": evaluation.id, + "criterion_score": criterion_score.id, + } + + await db_session.delete(tournament) + await db_session.commit() + + assert await db_session.get(Task, ids["task"]) is None + assert await db_session.get(Team, ids["team"]) is None + assert await db_session.get(TeamMember, ids["member"]) is None + assert await db_session.get(Submission, ids["submission"]) is None + assert await db_session.get(SubmissionUrl, ids["url"]) is None + assert await db_session.get(SubmissionEvaluation, ids["evaluation"]) is None + assert await db_session.get(CriterionScore, ids["criterion_score"]) is None + + +# TASK TESTS +async def test_create_task(create): + task = await create(TaskFactory) + + assert task.id is not None + assert task.title is not None + assert task.tournament_id is not None + assert task.status_id in [option["name"] for option in settings.TASK_STATUS_OPTIONS] + + +async def test_task_requirements_relationship(db_session, create): + category = await create(TaskRequirementCategoryFactory) + option = await create(TaskRequirementOptionFactory, category=category) + task = await create(TaskFactory, requirements=[option]) + + stmt = select(Task).where(Task.id == task.id).options(selectinload(Task.requirements)) + + result = await db_session.execute(stmt) + db_task = result.unique().scalar_one() + + assert len(db_task.requirements) == 1 + assert db_task.requirements[0].name == option.name + + +async def test_task_invalid_time(create): + task = await create( + TaskFactory, + start_time=datetime.now(), + end_time=datetime.now() - timedelta(hours=1), + ) + + assert task.end_time < task.start_time + + +async def test_task_category_hierarchy(db_session, create): + parent = await create(TaskRequirementCategoryFactory) + child = await create(TaskRequirementCategoryFactory, main_id=parent.name) + + stmt = ( + select(TaskRequirementCategory) + .where(TaskRequirementCategory.name == parent.name) + .options(selectinload(TaskRequirementCategory.sub_categories)) + ) + + result = await db_session.execute(stmt) + db_parent = result.unique().scalar_one() + + assert len(db_parent.sub_categories) == 1 + assert db_parent.sub_categories[0].name == child.name + assert db_parent.sub_categories[0].parent_category.name == parent.name + + +async def test_create_task_status_option(create): + status = await create(TaskStatusOptionFactory) + + assert status.name in [option["name"] for option in settings.TASK_STATUS_OPTIONS] + assert status.display_name in [ + option["display_name"] for option in settings.TASK_STATUS_OPTIONS + ] + + +async def test_task_status_relationship(db_session, create): + status = await create(TaskStatusOptionFactory) + task = await create(TaskFactory, status=status) + + stmt = select(Task).where(Task.id == task.id).options(selectinload(Task.status)) + + result = await db_session.execute(stmt) + db_task = result.scalar_one() + + assert db_task.status.name == status.name + + +# TOURNAMENT TESTS +async def test_create_tournament(create): + tournament = await create(TournamentFactory) + + assert tournament.title is not None + assert tournament.description is not None + assert tournament.max_teams is not None + + +async def test_tournament_end_time(create, db_session): + tournament1 = await create(TournamentFactory) + tournament2 = await create(TournamentFactory) + t1 = await create( + TaskFactory, + tournament=tournament1, + end_time=datetime.now() + timedelta(hours=12), + ) + t2 = await create( + TaskFactory, tournament=tournament1, end_time=datetime.now() + timedelta(days=1) + ) + t3 = await create( + TaskFactory, + tournament=tournament1, + end_time=datetime.now() + timedelta(weeks=1), + ) + assert tournament1.end_date == t3.end_time + assert not tournament2.end_date + + t2.tournament_id = tournament2.id + t1.tournament_id = tournament2.id + await db_session.commit() + + await db_session.refresh(tournament2) + assert tournament2.end_date == t2.end_time + + +async def test_tournament_active_task(create, db_session): + tournament = await create(TournamentFactory) + active = await create(TaskStatusOptionFactory, name=settings.TASK_STATUS_NAMES.ACTIVE) + submission_closed = await create( + TaskStatusOptionFactory, name=settings.TASK_STATUS_NAMES.SUBMISSION_CLOSED + ) + draft = await create(TaskStatusOptionFactory, name=settings.TASK_STATUS_NAMES.DRAFT) + t1 = await create(TaskFactory, tournament=tournament, status=active) + await create(TaskFactory, tournament=tournament, status_id=submission_closed) + await create(TaskFactory, tournament=tournament, status_id=draft) + assert tournament.active_task.id == t1.id + + +async def test_tournament_invalid_time(create): + tournament = await create( + TournamentFactory, + reg_start=datetime.now() + timedelta(days=1), + reg_end=datetime.now() - timedelta(hours=1), + ) + + assert tournament.reg_end < tournament.reg_start + + +async def test_tournament_without_creator(db_session, create): + with pytest.raises(IntegrityError): + await create(TournamentFactory, creator=None) + + await db_session.rollback() + + +async def test_tournament_creator_relationship(db_session, create): + tournament = await create(TournamentFactory) + + stmt = ( + select(Tournament) + .where(Tournament.id == tournament.id) + .options(selectinload(Tournament.creator)) + ) + + result = await db_session.execute(stmt) + db_tournament = result.scalar_one() + + assert db_tournament.creator is not None + assert db_tournament.creator.id == tournament.creator.id + + +async def test_tournament_status_relationship(db_session, create): + tournament = await create(TournamentFactory) + + stmt = ( + select(Tournament) + .where(Tournament.id == tournament.id) + .options(selectinload(Tournament.status)) + ) + + result = await db_session.execute(stmt) + db_tournament = result.scalar_one() + + assert db_tournament.status is not None + + +async def test_tournament_tasks_relationship(db_session, create): + tournament = await create(TournamentFactory) + + await create(TaskFactory, tournament=tournament) + await create(TaskFactory, tournament=tournament) + + stmt = ( + select(Tournament) + .where(Tournament.id == tournament.id) + .options(selectinload(Tournament.tasks)) + ) + + result = await db_session.execute(stmt) + db_tournament = result.scalar_one() + + assert len(db_tournament.tasks) == 2 + + +async def test_create_tournament_status_option(db_session, create): + tournament_status_option = await create(TournamentStatusOptionFactory) + + stmt = select(TournamentStatusOption).where( + TournamentStatusOption.name == tournament_status_option.name + ) + + result = await db_session.execute(stmt) + db_tournament_status_option = result.scalar_one() + + assert db_tournament_status_option.name == tournament_status_option.name + + +async def test_tournament_status_option_unique_name(db_session, create): + await create(TournamentStatusOptionFactory, name="Ongoing") + + with pytest.raises(IntegrityError): + await create(TournamentStatusOptionFactory, name="Ongoing") + + await db_session.rollback() + + +# SUBMISSION TESTS +async def test_create_submission(create): + submission = await create(SubmissionFactory) + + assert submission.team_id is not None + assert submission.team is not None + + +async def test_submission_urls_relationship(db_session, create): + submission = await create(SubmissionFactory) + await create(SubmissionUrlFactory, submission=submission) + await create(SubmissionUrlFactory, submission=submission) + + stmt = ( + select(Submission) + .where(Submission.team_id == submission.team_id) + .options(selectinload(Submission.urls)) + ) + + result = await db_session.execute(stmt) + db_submission = result.scalar_one() + + assert len(db_submission.urls) == 2 + + +async def test_multiple_submissions(create): + submissions1 = await create(SubmissionFactory) + submissions2 = await create(SubmissionFactory) + + assert submissions1.team_id is not None + assert submissions2.team_id is not None + assert submissions1.team_id != submissions2.team_id + + +async def test_submission_urls_belong_to_submission(create): + submission = await create(SubmissionFactory) + + url1 = await create(SubmissionUrlFactory, submission=submission) + url2 = await create(SubmissionUrlFactory, submission=submission) + + assert url1.submission_id == submission.team_id + assert url2.submission_id == submission.team_id + + +async def test_submission_url_has_submission(db_session, create): + url = await create(SubmissionUrlFactory) + + stmt = ( + select(SubmissionUrl) + .where(SubmissionUrl.submission_id == url.submission_id) + .options(selectinload(SubmissionUrl.submission)) + ) + + result = await db_session.execute(stmt) + db_url = result.scalar_one() + + assert db_url.submission is not None + + +async def test_submission_url_option_values(create): + option = await create(SubmissionUrlOptionFactory) + + assert option.name.startswith("url_option_") + assert option.display_name == option.name.upper() + + +# EVALUATION TESTS +async def test_create_submission_evaluation(create): + evaluation = await create(SubmissionEvaluationFactory) + + assert evaluation.id is not None + assert evaluation.submission is not None + assert evaluation.jury is not None + + +async def test_create_criterion_score(create): + evaluation = await create(CriterionScoreFactory) + + assert evaluation.evaluation_id is not None + assert evaluation.evaluation is not None + assert 0 <= evaluation.score <= 100 + + +async def test_evaluation_multiple_requirements(create): + evaluation = await create(SubmissionEvaluationFactory) + + req1 = await create(CriterionScoreFactory, evaluation=evaluation) + req2 = await create(CriterionScoreFactory, evaluation=evaluation) + + assert req1.evaluation_id == evaluation.id + assert req2.evaluation_id == evaluation.id + + +async def test_submission_evaluations_relationship(db_session, create): + submission = await create(SubmissionFactory) + + await create(SubmissionEvaluationFactory, submission=submission) + await create(SubmissionEvaluationFactory, submission=submission) + + stmt = ( + select(Submission) + .where(Submission.team_id == submission.team_id) + .options(selectinload(Submission.evaluations)) + ) + + result = await db_session.execute(stmt) + db_submission = result.scalar_one() + + assert len(db_submission.evaluations) == 2 + + +async def test_judge_cannot_evaluate_twice(create): + evaluation = await create(SubmissionEvaluationFactory) + + with pytest.raises(IntegrityError): + await create( + SubmissionEvaluationFactory, + submission=evaluation.submission, + jury=evaluation.jury, + ) + + +async def test_evaluation_jury_relationship(db_session, create): + evaluation = await create(SubmissionEvaluationFactory) + + stmt = ( + select(SubmissionEvaluation) + .where(SubmissionEvaluation.id == evaluation.id) + .options(selectinload(SubmissionEvaluation.jury)) + ) + + result = await db_session.execute(stmt) + db_eval = result.scalar_one() + + assert db_eval.jury.id == evaluation.jury.id + + +async def test_criterion_score_option_relationship(db_session, create): + option = await create(TaskEvaluationCriterionFactory) + + req_eval = await create( + CriterionScoreFactory, + criterion=option, + ) + + stmt = ( + select(CriterionScore) + .where(CriterionScore.id == req_eval.id) + .options(selectinload(CriterionScore.criterion)) + ) + + result = await db_session.execute(stmt) + db_req_eval = result.scalar_one() + + assert db_req_eval.criterion.name == option.name + + +# NOTIFICATION TESTS +async def test_create_notification(create): + notification = await create(NotificationFactory) + + assert notification.body is not None + assert notification.user is not None + + +async def test_user_notifications_relationship(db_session, create): + user = await create(UserFactory) + await create(NotificationFactory, user=user) + await create(NotificationFactory, user=user) + + stmt = select(User).where(User.id == user.id).options(selectinload(User.notifications)) + result = await db_session.execute(stmt) + db_user = result.scalar_one() + + assert len(db_user.notifications) == 2 + + +async def test_notification_user_relationship(db_session, create): + notification = await create(NotificationFactory) + + stmt = ( + select(Notification) + .where(Notification.id == notification.id) + .options(selectinload(Notification.user)) + ) + + result = await db_session.execute(stmt) + db_notification = result.scalar_one() + + assert db_notification.user.id == notification.user.id + + +# ROLE REQUEST TESTS +async def test_create_role_request(create): + role_request = await create(RoleRequestFactory) + + assert role_request.role is not None + assert role_request.user is not None + + +async def test_user_role_requests_relationship(db_session, create): + user = await create(UserFactory) + await create(RoleRequestFactory, user=user) + await create(RoleRequestFactory, user=user) + + stmt = select(User).where(User.id == user.id).options(selectinload(User.role_requests)) + result = await db_session.execute(stmt) + db_user = result.scalar_one() + + assert len(db_user.role_requests) == 2 + + +async def test_role_requests_relationship(db_session, create): + role = await create(RoleFactory) + await create(RoleRequestFactory, role=role) + await create(RoleRequestFactory, role=role) + + stmt = select(Role).where(role.name == role.name).options(selectinload(Role.requests)) + result = await db_session.execute(stmt) + db_role = result.scalar_one() + + assert len(db_role.requests) == 2 + + +# NEWS +async def test_create_news(create): + news = await create(NewsFactory) + + assert news.id is not None + assert news.body + + +async def test_create_news_important(db_session, create): + news = await create(NewsFactory, is_important=True) + notification = ( + await db_session.execute(select(Notification).where(Notification.body == news.excerpt)) + ).scalar_one_or_none() + assert notification + assert notification.id + assert notification.body + assert not notification.user_id + + +async def test_create_news_category(create): + category = await create(NewsCattegoryFactory) + + assert category.name in [option["name"] for option in settings.NEWS_CATEGORY_OPTIONS] + assert category.display_name in [ + option["display_name"] for option in settings.NEWS_CATEGORY_OPTIONS + ] diff --git a/backend/tests/test_utils.py b/backend/tests/test_utils.py new file mode 100644 index 0000000..f4f6f45 --- /dev/null +++ b/backend/tests/test_utils.py @@ -0,0 +1,20 @@ +import pytest +from app.utils import send_notification +from app.schemas import NotificationCreate +from .factories import UserFactory, RoleFactory +from app.config import settings +from app import app +from app.websockets import sio + + +@pytest.mark.asyncio +async def test_send_role_request_notification(db_session, create, mocker): + user = await create(UserFactory) + role = await create(RoleFactory) + notification = NotificationCreate( + body=settings.ROLE_REQUEST_NOTIFICATION_MESSAGE, user_id=user.id + ) + app.state.user_websocket_sessions[user.id] = {"sid": "test"} + spy = mocker.spy(sio, "emit") + await send_notification(notification, db_session, user=user.full_name, role=role.name) + assert spy.call_count == 1 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6ff52bc --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,59 @@ +services: + db: + image: postgres:16-alpine + environment: + POSTGRES_DB: tournament + POSTGRES_USER: tournament + POSTGRES_PASSWORD: tournament + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U tournament -d tournament"] + interval: 5s + timeout: 5s + retries: 10 + ports: + - "5432:5432" + + backend: + build: + context: . + dockerfile: backend/Dockerfile + depends_on: + db: + condition: service_healthy + environment: + SECRET_KEY: ${SECRET_KEY:-1234567890} + SQLALCHEMY_DATABASE_URI: postgresql+asyncpg://tournament:tournament@db:5432/tournament + FRONTEND_URL: ${FRONTEND_URL:-http://localhost:5173} + VITE_FIREBASE_API_KEY: ${VITE_FIREBASE_API_KEY:-} + VITE_FIREBASE_AUTH_DOMAIN: ${VITE_FIREBASE_AUTH_DOMAIN:-} + VITE_FIREBASE_PROJECT_ID: ${VITE_FIREBASE_PROJECT_ID:-} + VITE_FIREBASE_STORAGE_BUCKET: ${VITE_FIREBASE_STORAGE_BUCKET:-} + VITE_FIREBASE_MESSAGING_SENDER_ID: ${VITE_FIREBASE_MESSAGING_SENDER_ID:-} + VITE_FIREBASE_APP_ID: ${VITE_FIREBASE_APP_ID:-} + VITE_FIREBASE_MEASUREMENT_ID: ${VITE_FIREBASE_MEASUREMENT_ID:-} + ports: + - "8000:8000" + + frontend: + build: + context: . + dockerfile: frontend/Dockerfile + depends_on: + - backend + environment: + VITE_BACKEND_URL: ${VITE_BACKEND_URL:-http://localhost:8000} + VITE_SOCKETIO_SERVER_URL: ${VITE_SOCKETIO_SERVER_URL:-http://localhost:8000} + VITE_FIREBASE_API_KEY: ${VITE_FIREBASE_API_KEY:-} + VITE_FIREBASE_AUTH_DOMAIN: ${VITE_FIREBASE_AUTH_DOMAIN:-} + VITE_FIREBASE_PROJECT_ID: ${VITE_FIREBASE_PROJECT_ID:-} + VITE_FIREBASE_STORAGE_BUCKET: ${VITE_FIREBASE_STORAGE_BUCKET:-} + VITE_FIREBASE_MESSAGING_SENDER_ID: ${VITE_FIREBASE_MESSAGING_SENDER_ID:-} + VITE_FIREBASE_APP_ID: ${VITE_FIREBASE_APP_ID:-} + VITE_FIREBASE_MEASUREMENT_ID: ${VITE_FIREBASE_MEASUREMENT_ID:-} + ports: + - "5173:5173" + +volumes: + postgres_data: diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..54f07af --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? \ No newline at end of file diff --git a/frontend/.husky/pre-commit b/frontend/.husky/pre-commit new file mode 100644 index 0000000..d994b53 --- /dev/null +++ b/frontend/.husky/pre-commit @@ -0,0 +1,8 @@ +cd frontend +npx lint-staged +vitest + + +cd ../backend +python -m pytest +python -m ruff format \ No newline at end of file diff --git a/frontend/.prettierignore b/frontend/.prettierignore new file mode 100644 index 0000000..4fa5958 --- /dev/null +++ b/frontend/.prettierignore @@ -0,0 +1,6 @@ +# Ignore artifacts: +build +coverage +dist +star.json +hedgehog.json \ No newline at end of file diff --git a/frontend/.prettierrc b/frontend/.prettierrc new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/frontend/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..05423e4 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,14 @@ +FROM node:22-alpine + +WORKDIR /app/frontend + +COPY frontend/package.json frontend/package-lock.json ./ + +RUN npm ci + +COPY frontend ./ +COPY shared /app/shared + +EXPOSE 5173 + +CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "5173"] diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..8c2d760 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,73 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(["dist"]), + { + files: ["**/*.{ts,tsx}"], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ["./tsconfig.node.json", "./tsconfig.app.json"], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]); +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from "eslint-plugin-react-x"; +import reactDom from "eslint-plugin-react-dom"; + +export default defineConfig([ + globalIgnores(["dist"]), + { + files: ["**/*.{ts,tsx}"], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs["recommended-typescript"], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ["./tsconfig.node.json", "./tsconfig.app.json"], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]); +``` diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..75d3c46 --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,23 @@ +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; +import { defineConfig, globalIgnores } from "eslint/config"; + +export default defineConfig([ + globalIgnores(["dist"]), + { + files: ["**/*.{ts,tsx}"], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]); diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..6060587 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,45 @@ + + + + + + UGalaxy | Star for Life + + + + + + + + + + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..7b88331 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,8166 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@firebase-oss/ui-react": "^7.0.2-beta", + "@floating-ui/react": "^0.27.19", + "@headlessui/react": "^2.2.10", + "@hookform/resolvers": "^5.2.2", + "@lottiefiles/react-lottie-player": "^3.6.0", + "@reduxjs/toolkit": "^2.11.2", + "@tailwindcss/vite": "^4.2.1", + "@tanstack/react-query": "^5.95.2", + "@tanstack/react-query-devtools": "^5.95.2", + "axios": "1.13.6", + "clsx": "^2.1.1", + "date-fns": "^4.1.0", + "firebase": "^12.10.0", + "framer-motion": "^12.38.0", + "i18next": "^26.0.5", + "i18next-browser-languagedetector": "^8.2.1", + "i18next-http-backend": "^3.0.4", + "lottie-react": "^2.4.1", + "lucide-react": "^1.14.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-hook-form": "^7.72.0", + "react-hot-toast": "^2.6.0", + "react-i18next": "^17.0.3", + "react-icons": "^5.6.0", + "react-redux": "^9.2.0", + "react-router-dom": "^7.13.1", + "react-toastify": "^11.1.0", + "socket.io-client": "^4.8.3", + "sonner": "^2.0.7", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.2.1", + "zod": "^4.3.6" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@tanstack/eslint-plugin-query": "^5.95.2", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^24.10.1", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "@vitest/coverage-v8": "^4.1.4", + "@vitest/ui": "^4.1.4", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "husky": "^9.1.7", + "jsdom": "^29.0.1", + "lint-staged": "^17.0.2", + "prettier": "3.8.3", + "typescript": "~5.9.3", + "typescript-eslint": "^8.48.0", + "vite": "^7.3.1", + "vitest": "^4.1.2" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.5.tgz", + "integrity": "sha512-8cMAA1bE66Mb/tfmkhcfJLjEPgyT7SSy6lW6id5XL113ai1ky76d/1L27sGnXCMsLfq66DInAU3OzuahB4lu9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.7" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.6.tgz", + "integrity": "sha512-Tgmk6EQM0nc9xvp7sEHRVavbknhb/vGKht+04yAT3t5KQwZ02CSobCtcFgaHH04ZrjD1BhEKNA8tRhzFV20gkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.7" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "peer": true, + "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" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.2.tgz", + "integrity": "sha512-5GkLzz4prTIpoyeUiIu3iV6CSG3Plo7xRVOFPKI7FVEJ3mZ0A8SwK0XU3Gl7xAkiQ+mDyam+NNp875/C5y+jSA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@firebase-oss/ui-core": { + "version": "7.0.2-beta", + "resolved": "https://registry.npmjs.org/@firebase-oss/ui-core/-/ui-core-7.0.2-beta.tgz", + "integrity": "sha512-pm6u75QU/0BaDC7Z9qbRw/hHrUXUMXLzOgtvWHeH4+EtuJRMiC6JI+g/O2Pmenz4o5rKhSZfC3d58+6j0g5lOA==", + "license": "MIT", + "dependencies": { + "@firebase-oss/ui-translations": "7.0.2-beta", + "@nanostores/deepmap": "^0.0.1", + "libphonenumber-js": "^1.12.23", + "nanostores": "^1.1.0", + "qrcode-generator": "^2.0.4", + "zod": "4.1.12" + }, + "peerDependencies": { + "firebase": "^11 || ^12" + } + }, + "node_modules/@firebase-oss/ui-core/node_modules/zod": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", + "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@firebase-oss/ui-react": { + "version": "7.0.2-beta", + "resolved": "https://registry.npmjs.org/@firebase-oss/ui-react/-/ui-react-7.0.2-beta.tgz", + "integrity": "sha512-Kuxcu7nEe5XVBLxSKJfRuaU7VnbGKgMl0X4m3VKDfLqfCoXyj8BwhynXeX3r+JH7sQXe4DflgkPUvnekVNEyTg==", + "dependencies": { + "@firebase-oss/ui-core": "7.0.2-beta", + "@firebase-oss/ui-styles": "7.0.2-beta", + "@nanostores/react": "^1.0.0", + "@radix-ui/react-slot": "^1.2.3", + "@tanstack/react-form": "1.20.0", + "clsx": "^2.1.1", + "tailwind-merge": "^3.0.1", + "zod": "4.1.12" + }, + "peerDependencies": { + "firebase": "^11 || ^12", + "react": "^19", + "react-dom": "^19" + } + }, + "node_modules/@firebase-oss/ui-react/node_modules/zod": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", + "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@firebase-oss/ui-styles": { + "version": "7.0.2-beta", + "resolved": "https://registry.npmjs.org/@firebase-oss/ui-styles/-/ui-styles-7.0.2-beta.tgz", + "integrity": "sha512-o3MS/fe7072FlhpjCf0GAoY4Q1UravGYuHEDrauqgjjWcNJANGx9w8zXr0eNPJyRdneawZLd9iIQt/Lwu33mxQ==", + "dependencies": { + "cva": "1.0.0-beta.4" + } + }, + "node_modules/@firebase-oss/ui-translations": { + "version": "7.0.2-beta", + "resolved": "https://registry.npmjs.org/@firebase-oss/ui-translations/-/ui-translations-7.0.2-beta.tgz", + "integrity": "sha512-DFMcCJZv6qlaxPbkd3tUjn1ngT7i9Z31T2K9S1OawtlmWlwy7A7Lxon6g1OFSzc3mmUssoC7v0/muB8A3g2vTg==" + }, + "node_modules/@firebase/ai": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-2.9.0.tgz", + "integrity": "sha512-NPvBBuvdGo9x3esnABAucFYmqbBmXvyTMimBq2PCuLZbdANZoHzGlx7vfzbwNDaEtCBq4RGGNMliLIv6bZ+PtA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/component": "0.7.1", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/analytics": { + "version": "0.10.20", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.20.tgz", + "integrity": "sha512-adGTNVUWH5q66tI/OQuKLSN6mamPpfYhj0radlH2xt+3eL6NFPtXoOs+ulvs+UsmK27vNFx5FjRDfWk+TyduHg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.1", + "@firebase/installations": "0.6.20", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.26", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.26.tgz", + "integrity": "sha512-0j2ruLOoVSwwcXAF53AMoniJKnkwiTjGVfic5LDzqiRkR13vb5j6TXMeix787zbLeQtN/m1883Yv1TxI0gItbA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/analytics": "0.10.20", + "@firebase/analytics-types": "0.8.3", + "@firebase/component": "0.7.1", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/analytics-types": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz", + "integrity": "sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.9.tgz", + "integrity": "sha512-3gtUX0e584MYkKBQMgSECMvE1Dwzg+eONefDQ0wxVSe5YMBsZwdN5pL7UapwWBlV8+i8QCztF9TP947tEjZAGA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.1", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.14.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.11.1.tgz", + "integrity": "sha512-gmKfwQ2k8aUQlOyRshc+fOQLq0OwUmibIZvpuY1RDNu2ho0aTMlwxOuEiJeYOs7AxzhSx7gnXPFNsXCFbnvXUQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.1", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/app-check-compat": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.1.tgz", + "integrity": "sha512-yjSvSl5B1u4CirnxhzirN1uiTRCRfx+/qtfbyeyI+8Cx8Cw1RWAIO/OqytPSVwLYbJJ1vEC3EHfxazRaMoWKaA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check": "0.11.1", + "@firebase/app-check-types": "0.5.3", + "@firebase/component": "0.7.1", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz", + "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-check-types": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz", + "integrity": "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-compat": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.9.tgz", + "integrity": "sha512-e5LzqjO69/N2z7XcJeuMzIp4wWnW696dQeaHAUpQvGk89gIWHAIvG6W+mA3UotGW6jBoqdppEJ9DnuwbcBByug==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/app": "0.14.9", + "@firebase/component": "0.7.1", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", + "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@firebase/auth": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.12.1.tgz", + "integrity": "sha512-nXKj7d5bMBlnq6XpcQQpmnSVwEeHBkoVbY/+Wk0P1ebLSICoH4XPtvKOFlXKfIHmcS84mLQ99fk3njlDGKSDtw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.1", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@react-native-async-storage/async-storage": "^2.2.0" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@firebase/auth-compat": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.3.tgz", + "integrity": "sha512-nHOkupcYuGVxI1AJJ/OBhLPaRokbP14Gq4nkkoVvf1yvuREEWqdnrYB/CdsSnPxHMAnn5wJIKngxBF9jNX7s/Q==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth": "1.12.1", + "@firebase/auth-types": "0.13.0", + "@firebase/component": "0.7.1", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz", + "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/auth-types": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz", + "integrity": "sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.1.tgz", + "integrity": "sha512-mFzsm7CLHR60o08S23iLUY8m/i6kLpOK87wdEFPLhdlCahaxKmWOwSVGiWoENYSmFJJoDhrR3gKSCxz7ENdIww==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/data-connect": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.4.0.tgz", + "integrity": "sha512-vLXM6WHNIR3VtEeYNUb/5GTsUOyl3Of4iWNZHBe1i9f88sYFnxybJNWVBjvJ7flhCyF8UdxGpzWcUnv6F5vGfg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.7.1", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/database": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.1.tgz", + "integrity": "sha512-LwIXe8+mVHY5LBPulWECOOIEXDiatyECp/BOlu0gOhe+WOcKjWHROaCbLlkFTgHMY7RHr5MOxkLP/tltWAH3dA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.7.1", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.14.0", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.1.tgz", + "integrity": "sha512-heAEVZ9Z8c8PnBUcmGh91JHX0cXcVa1yESW/xkLuwaX7idRFyLiN8sl73KXpR8ZArGoPXVQDanBnk6SQiekRCQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.1", + "@firebase/database": "1.1.1", + "@firebase/database-types": "1.0.17", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.17.tgz", + "integrity": "sha512-4eWaM5fW3qEIHjGzfi3cf0Jpqi1xQsAdT6rSDE1RZPrWu8oGjgrq6ybMjobtyHQFgwGCykBm4YM89qDzc+uG/w==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-types": "0.9.3", + "@firebase/util": "1.14.0" + } + }, + "node_modules/@firebase/firestore": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.12.0.tgz", + "integrity": "sha512-PM47OyiiAAoAMB8kkq4Je14mTciaRoAPDd3ng3Ckqz9i2TX9D9LfxIRcNzP/OxzNV4uBKRq6lXoOggkJBQR3Gw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.1", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.14.0", + "@firebase/webchannel-wrapper": "1.0.5", + "@grpc/grpc-js": "~1.9.0", + "@grpc/proto-loader": "^0.7.8", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/firestore-compat": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.6.tgz", + "integrity": "sha512-NgVyR4hHHN2FvSNQOtbgBOuVsEdD/in30d9FKbEvvITiAChrBN2nBstmhfjI4EOTnHaP8zigwvkNYFI9yKGAkQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.1", + "@firebase/firestore": "4.12.0", + "@firebase/firestore-types": "3.0.3", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/firestore-types": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz", + "integrity": "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/functions": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.13.2.tgz", + "integrity": "sha512-tHduUD+DeokM3NB1QbHCvEMoL16e8Z8JSkmuVA4ROoJKPxHn8ibnecHPO2e3nVCJR1D9OjuKvxz4gksfq92/ZQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.7.1", + "@firebase/messaging-interop-types": "0.2.3", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/functions-compat": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.4.2.tgz", + "integrity": "sha512-YNxgnezvZDkqxqXa6cT7/oTeD4WXbxgIP7qZp4LFnathQv5o2omM6EoIhXiT9Ie5AoQDcIhG9Y3/dj+DFJGaGQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.1", + "@firebase/functions": "0.13.2", + "@firebase/functions-types": "0.6.3", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/functions-types": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz", + "integrity": "sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/installations": { + "version": "0.6.20", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.20.tgz", + "integrity": "sha512-LOzvR7XHPbhS0YB5ANXhqXB5qZlntPpwU/4KFwhSNpXNsGk/sBQ9g5hepi0y0/MfenJLe2v7t644iGOOElQaHQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.1", + "@firebase/util": "1.14.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/installations-compat": { + "version": "0.2.20", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.20.tgz", + "integrity": "sha512-9C9pL/DIEGucmoPj8PlZTnztbX3nhNj5RTYVpUM7wQq/UlHywaYv99969JU/WHLvi9ptzIogXYS9d1eZ6XFe9g==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.1", + "@firebase/installations": "0.6.20", + "@firebase/installations-types": "0.5.3", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/installations-types": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz", + "integrity": "sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/logger": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.0.tgz", + "integrity": "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/messaging": { + "version": "0.12.24", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.24.tgz", + "integrity": "sha512-UtKoubegAhHyehcB7iQjvQ8OVITThPbbWk3g2/2ze42PrQr6oe6OmCElYQkBrE5RDCeMTNucXejbdulrQ2XwVg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.1", + "@firebase/installations": "0.6.20", + "@firebase/messaging-interop-types": "0.2.3", + "@firebase/util": "1.14.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/messaging-compat": { + "version": "0.2.24", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.24.tgz", + "integrity": "sha512-wXH8FrKbJvFuFe6v98TBhAtvgknxKIZtGM/wCVsfpOGmaAE80bD8tBxztl+uochjnFb9plihkd6mC4y7sZXSpA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.1", + "@firebase/messaging": "0.12.24", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/messaging-interop-types": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz", + "integrity": "sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/performance": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.10.tgz", + "integrity": "sha512-8nRFld+Ntzp5cLKzZuG9g+kBaSn8Ks9dmn87UQGNFDygbmR6ebd8WawauEXiJjMj1n70ypkvAOdE+lzeyfXtGA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.1", + "@firebase/installations": "0.6.20", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0", + "web-vitals": "^4.2.4" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/performance-compat": { + "version": "0.2.23", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.23.tgz", + "integrity": "sha512-c7qOAGBUAOpIuUlHu1axWcrCVtIYKPMhH0lMnoCDWnPwn1HcPuPUBVTWETbC7UWw71RMJF8DpirfWXzMWJQfgA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.1", + "@firebase/logger": "0.5.0", + "@firebase/performance": "0.7.10", + "@firebase/performance-types": "0.2.3", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/performance-types": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz", + "integrity": "sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/remote-config": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.8.1.tgz", + "integrity": "sha512-L86TReBnPiiJOWd7k9iaiE9f7rHtMpjAoYN0fH2ey2ZRzsOChHV0s5sYf1+IIUYzplzsE46pjlmAUNkRRKwHSQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.1", + "@firebase/installations": "0.6.20", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat": { + "version": "0.2.22", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.22.tgz", + "integrity": "sha512-uW/eNKKtRBot2gnCC5mnoy5Voo2wMzZuQ7dwqqGHU176fO9zFgMwKiRzk+aaC99NLrFk1KOmr0ZVheD+zdJmjQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.1", + "@firebase/logger": "0.5.0", + "@firebase/remote-config": "0.8.1", + "@firebase/remote-config-types": "0.5.0", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/remote-config-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.5.0.tgz", + "integrity": "sha512-vI3bqLoF14L/GchtgayMiFpZJF+Ao3uR8WCde0XpYNkSokDpAKca2DxvcfeZv7lZUqkUwQPL2wD83d3vQ4vvrg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/storage": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.1.tgz", + "integrity": "sha512-uIpYgBBsv1vIET+5xV20XT7wwqV+H4GFp6PBzfmLUcEgguS4SWNFof56Z3uOC2lNDh0KDda1UflYq2VwD9Nefw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.1", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/storage-compat": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.1.tgz", + "integrity": "sha512-bgl3FHHfXAmBgzIK/Fps6Xyv2HiAQlSTov07CBL+RGGhrC5YIk4lruS8JVIC+UkujRdYvnf8cpQFGn2RCilJ/A==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.1", + "@firebase/storage": "0.14.1", + "@firebase/storage-types": "0.8.3", + "@firebase/util": "1.14.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/storage-types": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz", + "integrity": "sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/util": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.14.0.tgz", + "integrity": "sha512-/gnejm7MKkVIXnSJGpc9L2CvvvzJvtDPeAEq5jAwgVlf/PeNxot+THx/bpD20wQ8uL5sz0xqgXy1nisOYMU+mw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.5.tgz", + "integrity": "sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw==", + "license": "Apache-2.0" + }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.27.19", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.19.tgz", + "integrity": "sha512-31B8h5mm8YxotlE7/AU/PhNAl8eWxAmjL/v2QOxroDNkTFLk3Uu82u63N3b6TXa4EGJeeZLVcd/9AlNlVqzeog==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.8", + "@floating-ui/utils": "^0.2.11", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "node_modules/@grpc/grpc-js": { + "version": "1.9.15", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz", + "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.8", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@headlessui/react": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.10.tgz", + "integrity": "sha512-5pVLNK9wlpxTUTy9GpgbX/SdcRh+HBnPktjM2wbiLTH4p+2EPHBO1aoSryUCuKUIItdDWO9ITlhUL8UnUN/oIA==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.16", + "@react-aria/focus": "^3.20.2", + "@react-aria/interactions": "^3.25.0", + "@tanstack/react-virtual": "^3.13.9", + "use-sync-external-store": "^1.5.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/@headlessui/react/node_modules/@floating-ui/react": { + "version": "0.26.28", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", + "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@hookform/resolvers": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz", + "integrity": "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@internationalized/date": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.1.tgz", + "integrity": "sha512-6IedsVWXyq4P9Tj+TxuU8WGWM70hYLl12nbYU8jkikVpa6WXapFazPUcHUMDMoWftIDE2ILDkFFte6W2nFCkRQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.6.tgz", + "integrity": "sha512-iFgmQaXHE0vytNfpLZWOC2mEJCBRzcUxt53Xf/yCXG93lRvqas237i3r7X4RKMwO3txiyZD4mQjKAByFv6UGSQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/string": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.8.tgz", + "integrity": "sha512-NdbMQUSfXLYIQol5VyMtinm9pZDciiMfN7RtmSuSB78io1hqwJ0naYfxyW6vgxWBkzWymQa/3uLDlbfmshtCaA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lottiefiles/react-lottie-player": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@lottiefiles/react-lottie-player/-/react-lottie-player-3.6.0.tgz", + "integrity": "sha512-WK5TriLJT93VF3w4IjSVyveiedraZCnDhKzCPhpbeLgQeMi6zufxa3dXNc4HmAFRXq+LULPAy+Idv1rAfkReMA==", + "license": "MIT", + "dependencies": { + "lottie-web": "^5.12.2" + }, + "peerDependencies": { + "react": "16 - 19" + } + }, + "node_modules/@nanostores/deepmap": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@nanostores/deepmap/-/deepmap-0.0.1.tgz", + "integrity": "sha512-Wp7elfx/naWM61XOzC+qZOYQ565VVtUAU7NQSgjORRVSKn57U31GYHpn3TKImW7Vdfa9TAykIjzB7JVjH2L4fw==", + "license": "MIT", + "engines": { + "node": "^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "nanostores": "^1.0.0" + } + }, + "node_modules/@nanostores/react": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@nanostores/react/-/react-1.0.0.tgz", + "integrity": "sha512-eDduyNy+lbQJMg6XxZ/YssQqF6b4OXMFEZMYKPJCCmBevp1lg0g+4ZRi94qGHirMtsNfAWKNwsjOhC+q1gvC+A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": "^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "nanostores": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^1.0.0", + "react": ">=18.0.0" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@react-aria/focus": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.22.0.tgz", + "integrity": "sha512-ZfDOVuVhqDsM9mkNji3QUZ/d40JhlVgXrDkrfXylM1035QCrcTHN7m2DpbE95sU2A8EQb4wikvt5jM6K/73BPg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-aria": "3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/interactions": { + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.28.0.tgz", + "integrity": "sha512-OXwdU1EWFdMxmr/K1CXNGJzmNlCClByb+PuCaqUyzBymHPCGVhawirLIon/CrIN5psh3AiWpHSh4H0WeJdVpng==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.34.0", + "@swc/helpers": "^0.5.0", + "react-aria": "3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/shared": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.34.0.tgz", + "integrity": "sha512-gp6xo/s2lX54AlTjOiqwDnxA7UW79BNvI9dB9pr3LZTzRKCd1ZA+ZbgKw/ReIiWuvvVw/8QFJpnqeeFyLocMcQ==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", + "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz", + "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", + "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.31.1", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.1" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", + "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-x64": "4.2.1", + "@tailwindcss/oxide-freebsd-x64": "4.2.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-x64-musl": "4.2.1", + "@tailwindcss/oxide-wasm32-wasi": "4.2.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", + "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", + "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", + "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", + "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", + "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", + "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", + "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", + "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", + "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", + "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", + "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", + "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz", + "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.2.1", + "@tailwindcss/oxide": "4.2.1", + "tailwindcss": "4.2.1" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@tanstack/eslint-plugin-query": { + "version": "5.95.2", + "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.95.2.tgz", + "integrity": "sha512-EYUFRaqjBep4EHMPpZR12sXP7Kr5qv9iDIlq93NfbhHwhITaW6Txu3ROO6dLFz5r84T8p+oZXBG77pa2Wuok7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.48.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "^5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@tanstack/form-core": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@tanstack/form-core/-/form-core-1.20.0.tgz", + "integrity": "sha512-FGlKvcsusOf4756vtN1EoDI4h50r4/11eTcpF3NcnE04N/bSn2gP7cdhG6tYA0lJWzM9H1pNIzZ86uZ4MHB9eA==", + "license": "MIT", + "dependencies": { + "@tanstack/store": "^0.7.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.95.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.95.2.tgz", + "integrity": "sha512-o4T8vZHZET4Bib3jZ/tCW9/7080urD4c+0/AUaYVpIqOsr7y0reBc1oX3ttNaSW5mYyvZHctiQ/UOP2PfdmFEQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.95.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.95.2.tgz", + "integrity": "sha512-QfaoqBn9uAZ+ICkA8brd1EHj+qBF6glCFgt94U8XP5BT6ppSsDBI8IJ00BU+cAGjQzp6wcKJL2EmRYvxy0TWIg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-form": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-form/-/react-form-1.20.0.tgz", + "integrity": "sha512-1UfWqEYRnHr4cooGbHiTQqoqus8soNUH+RLD6UyhIQEvomOSQMX0JgX+zGSl08tIugrnWcAnh50n5T9IIs/Evw==", + "license": "MIT", + "dependencies": { + "@tanstack/form-core": "1.20.0", + "@tanstack/react-store": "^0.7.4", + "decode-formdata": "^0.9.0", + "devalue": "^5.3.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-start": "^1.130.10", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@tanstack/react-start": { + "optional": true + } + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.95.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.95.2.tgz", + "integrity": "sha512-/wGkvLj/st5Ud1Q76KF1uFxScV7WeqN1slQx5280ycwAyYkIPGaRZAEgHxe3bjirSd5Zpwkj6zNcR4cqYni/ZA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@tanstack/query-core": "5.95.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.95.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.95.2.tgz", + "integrity": "sha512-AFQFmbznVkbtfpx8VJ2DylW17wWagQel/qLstVLkYmNRo2CmJt3SNej5hvl6EnEeljJIdC3BTB+W7HZtpsH+3g==", + "license": "MIT", + "dependencies": { + "@tanstack/query-devtools": "5.95.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.95.2", + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-store": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.7.7.tgz", + "integrity": "sha512-qqT0ufegFRDGSof9D/VqaZgjNgp4tRPHZIJq2+QIHkMUtHjaJ0lYrrXjeIUJvjnTbgPfSD1XgOMEt0lmANn6Zg==", + "license": "MIT", + "dependencies": { + "@tanstack/store": "0.7.7", + "use-sync-external-store": "^1.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.13.24", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.24.tgz", + "integrity": "sha512-aIJvz5OSkhNIhZIpYivrxrPTKYsjW9Uzy+sP/mx0S3sev2HyvPb7xmjbYvokzEpfgYHy/HjzJ2zFAETuUfgCpg==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.14.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/store": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.7.7.tgz", + "integrity": "sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.14.0.tgz", + "integrity": "sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", + "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.0.tgz", + "integrity": "sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/type-utils": "8.56.0", + "@typescript-eslint/utils": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.56.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.0.tgz", + "integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.0.tgz", + "integrity": "sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.56.0", + "@typescript-eslint/types": "^8.56.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.0.tgz", + "integrity": "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.0.tgz", + "integrity": "sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.0.tgz", + "integrity": "sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/utils": "8.56.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.0.tgz", + "integrity": "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.0.tgz", + "integrity": "sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.56.0", + "@typescript-eslint/tsconfig-utils": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.0.tgz", + "integrity": "sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.0.tgz", + "integrity": "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz", + "integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz", + "integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.6.tgz", + "integrity": "sha512-36l628fQ/9a/8ihy97eOtEnvWQEdqULQOJtcaxtoNq0G1w3Mxd4szSahOaMM9/NGyZ+hyKcMtIW/WIxq0XQViQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.6", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.6", + "vitest": "4.1.6" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz", + "integrity": "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.6", + "@vitest/utils": "4.1.6", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.6.tgz", + "integrity": "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.6", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.6.tgz", + "integrity": "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.6.tgz", + "integrity": "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.6", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.6.tgz", + "integrity": "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.6", + "@vitest/utils": "4.1.6", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz", + "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/ui": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.6.tgz", + "integrity": "sha512-wiu5em68DfGv/2HFvI1Njr7JI2CHcBlQvereSzVG8my53PRxjTNOCsD9VOkRKrsJBDHmyuXvosxWZw7T91a2mw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/utils": "4.1.6", + "fflate": "^0.8.2", + "flatted": "^3.4.2", + "pathe": "^2.0.3", + "sirv": "^3.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "4.1.6" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.6.tgz", + "integrity": "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.6", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", + "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001770", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", + "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", + "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT", + "peer": true + }, + "node_modules/cva": { + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/cva/-/cva-1.0.0-beta.4.tgz", + "integrity": "sha512-F/JS9hScapq4DBVQXcK85l9U91M6ePeXoBMSp7vypzShoefUBxjQTo3g3935PUHgQd+IW77DjbPRIxugy4/GCQ==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + }, + "peerDependencies": { + "typescript": ">= 4.5.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/decode-formdata": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/decode-formdata/-/decode-formdata-0.9.0.tgz", + "integrity": "sha512-q5uwOjR3Um5YD+ZWPOF/1sGHVW9A5rCrRwITQChRXlmPkxDFBqCm4jNTIVdGHNH9OnR+V9MoZVgRhsFb+ARbUw==", + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.4.tgz", + "integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==", + "license": "MIT" + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/engine.io-client": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz", + "integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.18.3", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/firebase": { + "version": "12.10.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-12.10.0.tgz", + "integrity": "sha512-tAjHnEirksqWpa+NKDUSUMjulOnsTcsPC1X1rQ+gwPtjlhJS572na91CwaBXQJHXharIrfj7sw/okDkXOsphjA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/ai": "2.9.0", + "@firebase/analytics": "0.10.20", + "@firebase/analytics-compat": "0.2.26", + "@firebase/app": "0.14.9", + "@firebase/app-check": "0.11.1", + "@firebase/app-check-compat": "0.4.1", + "@firebase/app-compat": "0.5.9", + "@firebase/app-types": "0.9.3", + "@firebase/auth": "1.12.1", + "@firebase/auth-compat": "0.6.3", + "@firebase/data-connect": "0.4.0", + "@firebase/database": "1.1.1", + "@firebase/database-compat": "2.1.1", + "@firebase/firestore": "4.12.0", + "@firebase/firestore-compat": "0.4.6", + "@firebase/functions": "0.13.2", + "@firebase/functions-compat": "0.4.2", + "@firebase/installations": "0.6.20", + "@firebase/installations-compat": "0.2.20", + "@firebase/messaging": "0.12.24", + "@firebase/messaging-compat": "0.2.24", + "@firebase/performance": "0.7.10", + "@firebase/performance-compat": "0.2.23", + "@firebase/remote-config": "0.8.1", + "@firebase/remote-config-compat": "0.2.22", + "@firebase/storage": "0.14.1", + "@firebase/storage-compat": "0.4.1", + "@firebase/util": "1.14.0" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/framer-motion": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz", + "integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.38.0", + "motion-utils": "^12.36.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/goober": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz", + "integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "license": "MIT" + }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/i18next": { + "version": "26.0.5", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.0.5.tgz", + "integrity": "sha512-9uHb4T27TdV36phJXcbpnRPt5yzAfqHXVrdASvmHZyPuZJtrLythd+GyXhiaHV5LlpuuskbAqhwPjmfTbKbi8w==", + "funding": [ + { + "type": "individual", + "url": "https://www.locize.com/i18next" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + }, + { + "type": "individual", + "url": "https://www.locize.com" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.29.2" + }, + "peerDependencies": { + "typescript": "^5 || ^6" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz", + "integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-http-backend": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.4.tgz", + "integrity": "sha512-udwrBIE6cNpqn1gRAqRULq3+7MzIIuaiKRWrz++dVz5SqWW2VwXmPJtAgkI0JtMLFaADC9qNmnZAxWAhsxXx2g==", + "license": "MIT", + "dependencies": { + "cross-fetch": "4.1.0" + } + }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "license": "ISC" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz", + "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.1.tgz", + "integrity": "sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.0.1", + "@asamuzakjp/dom-selector": "^7.0.3", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.1", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.7", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.24.5", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.12.39", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.39.tgz", + "integrity": "sha512-MW79m7HuOqBk8mwytiXYTMELJiBbV3Zl9Y39dCCn1yC8K+WGNSq1QGvzywbylp5vGShEztMScCWHX/XFOS0rXg==", + "license": "MIT" + }, + "node_modules/lightningcss": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", + "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.31.1", + "lightningcss-darwin-arm64": "1.31.1", + "lightningcss-darwin-x64": "1.31.1", + "lightningcss-freebsd-x64": "1.31.1", + "lightningcss-linux-arm-gnueabihf": "1.31.1", + "lightningcss-linux-arm64-gnu": "1.31.1", + "lightningcss-linux-arm64-musl": "1.31.1", + "lightningcss-linux-x64-gnu": "1.31.1", + "lightningcss-linux-x64-musl": "1.31.1", + "lightningcss-win32-arm64-msvc": "1.31.1", + "lightningcss-win32-x64-msvc": "1.31.1" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", + "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lint-staged": { + "version": "17.0.4", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.0.4.tgz", + "integrity": "sha512-+rU9lSUyVOZ/hDUmRLVGzyS2v73cDdQjX+XQz1AaOdIE4RysLq0HoPW2HrrgeNCLklkhi904VBU1bmgWLHVnkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "listr2": "^10.2.1", + "picomatch": "^4.0.4", + "string-argv": "^0.3.2", + "tinyexec": "^1.1.2" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=22.22.1" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + }, + "optionalDependencies": { + "yaml": "^2.8.4" + } + }, + "node_modules/listr2": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-10.2.1.tgz", + "integrity": "sha512-7I5knELsJKTUjXG+A6BkKAiGkW1i25fNa/xlUl9hFtk15WbE9jndA89xu5FzQKrY5llajE1hfZZFMILXkDHk/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.2.0", + "eventemitter3": "^5.0.4", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^10.0.0" + }, + "engines": { + "node": ">=22.13.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/string-width": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-10.0.0.tgz", + "integrity": "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "string-width": "^8.2.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lottie-react": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lottie-react/-/lottie-react-2.4.1.tgz", + "integrity": "sha512-LQrH7jlkigIIv++wIyrOYFLHSKQpEY4zehPicL9bQsrt1rnoKRYCYgpCUe5maqylNtacy58/sQDZTkwMcTRxZw==", + "license": "MIT", + "dependencies": { + "lottie-web": "^5.10.2" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/lottie-web": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/lottie-web/-/lottie-web-5.13.0.tgz", + "integrity": "sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.14.0.tgz", + "integrity": "sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/motion-dom": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz", + "integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.36.0" + } + }, + "node_modules/motion-utils": { + "version": "12.36.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz", + "integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==", + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanostores": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-1.1.1.tgz", + "integrity": "sha512-EYJqS25r2iBeTtGQCHidXl1VfZ1jXM7Q04zXJOrMlxVVmD0ptxJaNux92n1mJ7c5lN3zTq12MhH/8x59nP+qmg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": "^20.0.0 || >=22.0.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qrcode-generator": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-2.0.4.tgz", + "integrity": "sha512-mZSiP6RnbHl4xL2Ap5HfkjLnmxfKcPWpWe/c+5XxCuetEenqmNFf1FH/ftXPCtFG5/TDobjsjz6sSNL0Sr8Z9g==", + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-aria": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.48.0.tgz", + "integrity": "sha512-jQjd4rBEIMqecBaAKYJbVGK6EqIHLa5znVQ7jwFyK5vCyljoj6KhgtiahmcIPsG5vG5vEDLw+ba+bEWn6A2P4w==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.12.1", + "@internationalized/number": "^3.6.6", + "@internationalized/string": "^3.2.8", + "@react-types/shared": "^3.34.0", + "@swc/helpers": "^0.5.0", + "aria-hidden": "^1.2.3", + "clsx": "^2.0.0", + "react-stately": "3.46.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-hook-form": { + "version": "7.72.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.72.1.tgz", + "integrity": "sha512-RhwBoy2ygeVZje+C+bwJ8g0NjTdBmDlJvAUHTxRjTmSUKPYsKfMphkS2sgEMotsY03bP358yEYlnUeZy//D9Ig==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, + "node_modules/react-hot-toast": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", + "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/react-i18next": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.3.tgz", + "integrity": "sha512-x4xjvUNZ56T+zfXWNedNnCET9Xq1IBYWX7IsWo5cCQ/RT+Rm7GWqt0h9PShFi4IhyMnsdiu1C6Jc4DE+/S3PFQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "html-parse-stringify": "^3.0.1", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "i18next": ">= 26.0.1", + "react": ">= 16.8.0", + "typescript": "^5 || ^6" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/react-icons": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.6.0.tgz", + "integrity": "sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz", + "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz", + "integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==", + "license": "MIT", + "dependencies": { + "react-router": "7.13.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-stately": { + "version": "3.46.0", + "resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.46.0.tgz", + "integrity": "sha512-OdxhWvHgs2L4OJGIs7hnuTr5WjjMM6enhNEAMRqiekhF8+ITvA2LRwNftOZwcogaoCslGYq5S2VQTQwnm0GbCA==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.12.1", + "@internationalized/number": "^3.6.6", + "@internationalized/string": "^3.2.8", + "@react-types/shared": "^3.34.0", + "@swc/helpers": "^0.5.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/react-toastify": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.1.0.tgz", + "integrity": "sha512-e9h23x3phN0wbFeB6yovmWp7lobzV4CaCH0LO8nVP6H7Y+3GbcLpIzMm9dJhcp1RXbpyfvjgpfXqO80QAmn7sg==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT", + "peer": true + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/socket.io-client": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", + "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", + "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/sonner": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "license": "MIT" + }, + "node_modules/tailwind-merge": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", + "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", + "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.27.tgz", + "integrity": "sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.27" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.27.tgz", + "integrity": "sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.0.tgz", + "integrity": "sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.56.0", + "@typescript-eslint/parser": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/utils": "8.56.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz", + "integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.6.tgz", + "integrity": "sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/expect": "4.1.6", + "@vitest/mocker": "4.1.6", + "@vitest/pretty-format": "4.1.6", + "@vitest/runner": "4.1.6", + "@vitest/snapshot": "4.1.6", + "@vitest/spy": "4.1.6", + "@vitest/utils": "4.1.6", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.6", + "@vitest/browser-preview": "4.1.6", + "@vitest/browser-webdriverio": "4.1.6", + "@vitest/coverage-istanbul": "4.1.6", + "@vitest/coverage-v8": "4.1.6", + "@vitest/ui": "4.1.6", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/web-vitals": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", + "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==", + "license": "Apache-2.0" + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "optional": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..d8ce1ba --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,80 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview", + "test": "vitest", + "test:ui": "vitest --ui", + "test:coverage": "vitest run --coverage", + "prepare": "cd .. && husky frontend/.husky" + }, + "dependencies": { + "@firebase-oss/ui-react": "^7.0.2-beta", + "@floating-ui/react": "^0.27.19", + "@headlessui/react": "^2.2.10", + "@hookform/resolvers": "^5.2.2", + "@lottiefiles/react-lottie-player": "^3.6.0", + "@reduxjs/toolkit": "^2.11.2", + "@tailwindcss/vite": "^4.2.1", + "@tanstack/react-query": "^5.95.2", + "@tanstack/react-query-devtools": "^5.95.2", + "axios": "1.13.6", + "clsx": "^2.1.1", + "date-fns": "^4.1.0", + "firebase": "^12.10.0", + "framer-motion": "^12.38.0", + "i18next": "^26.0.5", + "i18next-browser-languagedetector": "^8.2.1", + "i18next-http-backend": "^3.0.4", + "lottie-react": "^2.4.1", + "lucide-react": "^1.14.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-hook-form": "^7.72.0", + "react-hot-toast": "^2.6.0", + "react-i18next": "^17.0.3", + "react-icons": "^5.6.0", + "react-redux": "^9.2.0", + "react-router-dom": "^7.13.1", + "react-toastify": "^11.1.0", + "socket.io-client": "^4.8.3", + "sonner": "^2.0.7", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.2.1", + "zod": "^4.3.6" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@tanstack/eslint-plugin-query": "^5.95.2", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^24.10.1", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "@vitest/coverage-v8": "^4.1.4", + "@vitest/ui": "^4.1.4", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "husky": "^9.1.7", + "jsdom": "^29.0.1", + "lint-staged": "^17.0.2", + "prettier": "3.8.3", + "typescript": "~5.9.3", + "typescript-eslint": "^8.48.0", + "vite": "^7.3.1", + "vitest": "^4.1.2" + }, + "lint-staged": { + "*.js": "eslint --cache --fix", + "*.{js,css,md}": "prettier --write" + } +} diff --git a/frontend/public/hedgehog.json b/frontend/public/hedgehog.json new file mode 100644 index 0000000..ab70e3a --- /dev/null +++ b/frontend/public/hedgehog.json @@ -0,0 +1 @@ +{"tgs":1,"v":"5.5.2.2","fr":60,"ip":0,"op":180,"w":512,"h":512,"nm":"10_HELLO_OUT","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"babo","parent":17,"sr":1,"ks":{"r":{"a":0,"k":0.249},"p":{"a":0,"k":[9.417,84.483,0]},"s":{"a":0,"k":[133.988,128.79,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.37,"y":1},"o":{"x":0.706,"y":0},"t":60,"s":[{"i":[[0,0],[0,0],[-0.957,2.063],[0,0],[-15.17,-7.792],[0,0]],"o":[[0,0],[0,0],[0.957,-2.063],[0,0],[1.928,0.99],[0,0]],"v":[[0.119,-1.315],[-33.227,-12.709],[-26.419,17.408],[0.119,-1.315],[32.37,15.49],[32.37,-17.503]],"c":true}]},{"t":120,"s":[{"i":[[0,0],[0,0],[0.428,-7.414],[0,0],[-13.306,-2.465],[0,0]],"o":[[0,0],[0,0],[-0.131,2.271],[0,0],[2.131,0.395],[0,0]],"v":[[0.119,-1.315],[-28.08,-16.522],[-37.658,11.938],[0.119,-1.315],[22.391,7.035],[35.15,-18.93]],"c":true}],"h":1},{"i":{"x":0.667,"y":1},"o":{"x":0.63,"y":0},"t":156,"s":[{"i":[[0,0],[0,0],[0.428,-7.414],[0,0],[-13.306,-2.465],[0,0]],"o":[[0,0],[0,0],[-0.131,2.271],[0,0],[2.131,0.395],[0,0]],"v":[[0.119,-1.315],[-28.08,-16.522],[-37.658,11.938],[0.119,-1.315],[22.391,7.035],[35.15,-18.93]],"c":true}]},{"t":176,"s":[{"i":[[0,0],[0,0],[-0.957,2.063],[0,0],[-15.17,-7.792],[0,0]],"o":[[0,0],[0,0],[0.957,-2.063],[0,0],[1.928,0.99],[0,0]],"v":[[0.119,-1.315],[-33.227,-12.709],[-26.419,17.408],[0.119,-1.315],[32.37,15.49],[32.37,-17.503]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.239215686275,0.2,0.2,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.240594362745,0.199831899007,0.199831899007,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"hat_t 4","parent":3,"sr":1,"ks":{},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":75,"s":[{"i":[[-0.966,-11.915],[77.038,-0.149],[0.966,11.915],[-77.038,0.149]],"o":[[0.966,11.915],[-77.038,0.149],[-0.966,-11.915],[77.038,-0.149]],"v":[[139.49,-0.27],[1.748,21.574],[-139.49,0.27],[-1.748,-21.574]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":102,"s":[{"i":[[-0.587,-0.574],[46.818,-0.007],[0.587,0.574],[-46.818,0.007]],"o":[[0.587,0.574],[-46.818,0.007],[-0.587,-0.574],[46.818,-0.007]],"v":[[84.771,-0.013],[1.063,1.039],[-84.771,0.013],[-1.063,-1.039]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":131,"s":[{"i":[[-0.631,4.221],[50.323,0.053],[0.631,-4.221],[-50.323,-0.053]],"o":[[0.631,-4.221],[-50.323,-0.053],[-0.631,4.221],[50.323,0.053]],"v":[[91.117,0.096],[1.142,-7.642],[-91.117,-0.096],[-1.142,7.642]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":146,"s":[{"i":[[-0.631,4.221],[50.323,0.053],[0.631,-4.221],[-50.323,-0.053]],"o":[[0.631,-4.221],[-50.323,-0.053],[-0.631,4.221],[50.323,0.053]],"v":[[91.117,0.096],[1.142,-7.642],[-91.117,-0.096],[-1.142,7.642]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":156,"s":[{"i":[[-0.587,-0.574],[46.818,-0.007],[0.587,0.574],[-46.818,0.007]],"o":[[0.587,0.574],[-46.818,0.007],[-0.587,-0.574],[46.818,-0.007]],"v":[[84.771,-0.013],[1.063,1.039],[-84.771,0.013],[-1.063,-1.039]],"c":true}]},{"t":166,"s":[{"i":[[-0.966,-11.915],[77.038,-0.149],[0.966,11.915],[-77.038,0.149]],"o":[[0.966,11.915],[-77.038,0.149],[-0.966,-11.915],[77.038,-0.149]],"v":[[139.49,-0.27],[1.748,21.574],[-139.49,0.27],[-1.748,-21.574]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.671292892157,0.4375980452,0.178795489143,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.670588235294,0.439215686275,0.180392156863,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":107,"op":154,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"hat_t 2","parent":8,"sr":1,"ks":{},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":75,"s":[{"i":[[-0.966,-11.915],[77.038,-0.149],[0.966,11.915],[-77.038,0.149]],"o":[[0.966,11.915],[-77.038,0.149],[-0.966,-11.915],[77.038,-0.149]],"v":[[139.49,-0.27],[1.748,21.574],[-139.49,0.27],[-1.748,-21.574]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":102,"s":[{"i":[[-0.966,0.05],[77.038,0.001],[0.966,-0.05],[-77.038,-0.001]],"o":[[0.966,-0.05],[-77.038,-0.001],[-0.966,0.05],[77.038,0.001]],"v":[[139.49,0.001],[1.748,-0.091],[-139.49,-0.001],[-1.748,0.091]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":131,"s":[{"i":[[-0.966,12.999],[77.038,0.163],[0.966,-12.999],[-77.038,-0.163]],"o":[[0.966,-12.999],[-77.038,-0.163],[-0.966,12.999],[77.038,0.163]],"v":[[139.49,0.295],[1.748,-23.537],[-139.49,-0.295],[-1.748,23.537]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":146,"s":[{"i":[[-0.966,12.999],[77.038,0.163],[0.966,-12.999],[-77.038,-0.163]],"o":[[0.966,-12.999],[-77.038,-0.163],[-0.966,12.999],[77.038,0.163]],"v":[[139.49,0.295],[1.748,-23.537],[-139.49,-0.295],[-1.748,23.537]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":156,"s":[{"i":[[-0.966,0.05],[77.038,0.001],[0.966,-0.05],[-77.038,-0.001]],"o":[[0.966,-0.05],[-77.038,-0.001],[-0.966,0.05],[77.038,0.001]],"v":[[139.49,0.001],[1.748,-0.091],[-139.49,-0.001],[-1.748,0.091]],"c":true}]},{"t":166,"s":[{"i":[[-0.966,-11.915],[77.038,-0.149],[0.966,11.915],[-77.038,0.149]],"o":[[0.966,11.915],[-77.038,0.149],[-0.966,-11.915],[77.038,-0.149]],"v":[[139.49,-0.27],[1.748,21.574],[-139.49,0.27],[-1.748,-21.574]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.866666666667,0.650980392157,0.16862745098,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.831372559071,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":102,"op":157,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Layer 2 copy","parent":5,"sr":1,"ks":{"r":{"a":0,"k":2.916},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":74,"s":[-45.77,2.322,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.167,"y":0.167},"t":101,"s":[-41.111,-9.121,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"t":130,"s":[-41.111,-9.121,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":145,"s":[-41.111,-9.121,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":155,"s":[-41.111,-9.121,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.167,"y":0.167},"t":165,"s":[-45.77,2.322,0],"to":[0,0,0],"ti":[0,0,0]},{"t":176,"s":[-45.77,2.322,0]}]},"a":{"a":0,"k":[1525.054,-1499.111,0]},"s":{"a":0,"k":[70.783,68.009,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":176,"s":[{"i":[[0,-9.711],[9.711,0],[0,9.711],[-9.711,0]],"o":[[0,9.711],[-9.711,0],[0,-9.711],[9.711,0]],"v":[[17.584,0],[0,17.584],[-17.584,0],[0,-17.584]],"c":true}]},{"i":{"x":0.37,"y":1},"o":{"x":0.333,"y":0},"t":202,"s":[{"i":[[0,-9.711],[8.657,0],[0,9.711],[-8.657,0]],"o":[[0,9.711],[-8.657,0],[0,-9.711],[8.657,0]],"v":[[4.621,0.331],[-11.054,17.915],[-26.729,0.331],[-11.054,-17.252]],"c":true}]},{"t":225,"s":[{"i":[[0,-9.711],[9.711,0],[0,9.711],[-9.711,0]],"o":[[0,9.711],[-9.711,0],[0,-9.711],[9.711,0]],"v":[[17.584,0],[0,17.584],[-17.584,0],[0,-17.584]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.831372559071,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[1525.054,-1499.111]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":176,"s":[{"i":[[2.945,5.101],[11.372,-2.974],[5.891,0],[3.11,-11.335],[2.945,-5.101],[-8.261,-8.361],[-2.945,-5.101],[-11.372,2.974],[-5.891,0],[-3.11,11.335],[-2.945,5.101],[8.261,8.361]],"o":[[-2.945,-5.101],[-3.11,-11.335],[-5.891,0],[-11.372,-2.974],[-2.945,5.101],[-8.261,8.361],[2.945,5.101],[3.11,11.335],[5.891,0],[11.372,2.974],[2.945,-5.101],[8.261,-8.361]],"v":[[37.553,-21.681],[14.199,-24.593],[0,-43.362],[-14.199,-24.593],[-37.553,-21.681],[-28.398,0],[-37.553,21.681],[-14.199,24.593],[0,43.362],[14.199,24.593],[37.553,21.681],[28.398,0]],"c":true}]},{"i":{"x":0.37,"y":1},"o":{"x":0.333,"y":0},"t":202,"s":[{"i":[[2.626,5.101],[10.137,-2.974],[5.251,0],[2.773,-11.335],[2.626,-5.101],[-7.365,-8.361],[-2.626,-5.101],[-10.137,2.974],[-5.251,0],[-2.773,11.335],[-2.625,5.101],[7.365,8.361]],"o":[[-2.626,-5.101],[-2.773,-11.335],[-5.251,0],[-10.137,-2.974],[-2.626,5.101],[-7.365,8.361],[2.626,5.101],[2.773,11.335],[5.251,0],[10.137,2.974],[2.625,-5.101],[7.365,-8.361]],"v":[[22.422,-21.35],[1.604,-24.262],[-11.054,-43.031],[-23.712,-24.262],[-44.53,-21.35],[-36.369,0.332],[-44.53,22.012],[-23.712,24.925],[-11.054,43.693],[1.604,24.925],[22.422,22.012],[14.261,0.332]],"c":true}]},{"t":225,"s":[{"i":[[2.945,5.101],[11.372,-2.974],[5.891,0],[3.11,-11.335],[2.945,-5.101],[-8.261,-8.361],[-2.945,-5.101],[-11.372,2.974],[-5.891,0],[-3.11,11.335],[-2.945,5.101],[8.261,8.361]],"o":[[-2.945,-5.101],[-3.11,-11.335],[-5.891,0],[-11.372,-2.974],[-2.945,5.101],[-8.261,8.361],[2.945,5.101],[3.11,11.335],[5.891,0],[11.372,2.974],[2.945,-5.101],[8.261,-8.361]],"v":[[37.553,-21.681],[14.199,-24.593],[0,-43.362],[-14.199,-24.593],[-37.552,-21.681],[-28.398,0],[-37.552,21.681],[-14.199,24.593],[0,43.362],[14.199,24.593],[37.553,21.681],[28.398,0]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.509803950787,0.803921580315,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[1525.054,-1499.111]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false}],"ip":-1,"op":299,"st":-1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"hat_black","parent":8,"sr":1,"ks":{"p":{"a":0,"k":[-0.661,-23.478,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":75,"s":[{"i":[[0,0],[12.714,6.89],[1.026,-8.218],[1.259,11.181]],"o":[[-15.318,6.477],[-0.258,4.524],[-1.026,8.218],[-1.259,-11.181]],"v":[[83.133,-8.581],[-82.398,-8.577],[-86.575,21.345],[86.575,22.828]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":101,"s":[{"i":[[0,0],[19.034,-0.767],[1.026,-8.218],[-32.446,0.233]],"o":[[-25.775,-1.653],[-0.258,4.524],[19.897,0.022],[-1.259,-11.181]],"v":[[83.174,-11.632],[-82.358,-11.628],[-85.237,15.641],[84.989,15.519]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":131,"s":[{"i":[[0,0],[15.754,-18.11],[1.026,-8.218],[1.259,11.181]],"o":[[-19.403,-16.302],[-0.258,4.524],[-1.026,8.218],[-1.259,-11.181]],"v":[[83.133,-8.581],[-82.398,-8.577],[-86.575,21.345],[86.575,22.828]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":146,"s":[{"i":[[0,0],[15.754,-18.11],[1.026,-8.218],[1.259,11.181]],"o":[[-19.403,-16.302],[-0.258,4.524],[-1.026,8.218],[-1.259,-11.181]],"v":[[83.133,-8.581],[-82.398,-8.577],[-86.575,21.345],[86.575,22.828]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":156,"s":[{"i":[[0,0],[19.034,-0.767],[1.026,-8.218],[-32.446,0.233]],"o":[[-25.775,-1.653],[-0.258,4.524],[19.897,0.022],[-1.259,-11.181]],"v":[[83.174,-11.632],[-82.358,-11.628],[-85.237,15.641],[84.989,15.519]],"c":true}]},{"t":166,"s":[{"i":[[0,0],[12.714,6.89],[1.026,-8.218],[1.259,11.181]],"o":[[-15.318,6.477],[-0.258,4.524],[-1.026,8.218],[-1.259,-11.181]],"v":[[83.133,-8.581],[-82.398,-8.577],[-86.575,21.345],[86.575,22.828]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019613862,0.156862750649,0.152941182256,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":269,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"HAT_b 2","parent":7,"sr":1,"ks":{"o":{"a":1,"k":[{"t":60,"s":[100],"h":1},{"t":102,"s":[0],"h":1},{"t":156,"s":[100],"h":1}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":75,"s":[{"i":[[0,0],[-3.874,13.602]],"o":[[3.895,11.825],[-0.546,-2.792]],"v":[[-73.169,-27.891],[75.994,-27.905]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":102,"s":[{"i":[[0,0],[-3.874,1.084]],"o":[[3.895,0.942],[-0.546,-0.222]],"v":[[-73.169,-30.473],[75.994,-30.474]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":156,"s":[{"i":[[0,0],[-8.907,-4.587]],"o":[[8.546,-4.94],[-0.546,-0.222]],"v":[[-74.391,-26.962],[76.179,-28.202]],"c":false}]},{"t":166,"s":[{"i":[[0,0],[-3.874,13.602]],"o":[[3.895,11.825],[-0.546,-2.792]],"v":[[-73.169,-27.891],[75.994,-27.905]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":20},"e":{"a":0,"k":100},"o":{"a":0,"k":0},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.866666666667,0.650980392157,0.16862745098,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":7},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":289,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"HAT_b","parent":8,"sr":1,"ks":{"p":{"a":0,"k":[0,-49.472,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":75,"s":[{"i":[[0,0],[2.168,-11.913],[1.195,-17.217],[1.162,12.183]],"o":[[-2.362,-13.992],[0.546,2.792],[-1.195,17.217],[-1.162,-12.183]],"v":[[76.183,-27.535],[-75.431,-27.53],[-82.067,39.59],[82.067,41.073]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":102,"s":[{"i":[[0,0],[10.725,-1.741],[1.195,-17.217],[1.162,12.183]],"o":[[-17.284,-5.966],[0.546,2.792],[-1.195,17.217],[-1.162,-12.183]],"v":[[76.183,-27.535],[-75.431,-27.53],[-82.067,39.59],[82.067,41.073]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":131,"s":[{"i":[[0,0],[3.425,-21.933],[1.195,-17.217],[1.162,12.183]],"o":[[-2.631,-24.209],[0.546,2.792],[-1.195,17.217],[-1.162,-12.183]],"v":[[76.183,-27.535],[-75.431,-27.53],[-82.067,39.59],[82.067,41.073]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":146,"s":[{"i":[[0,0],[3.425,-21.933],[1.195,-17.217],[1.162,12.183]],"o":[[-2.631,-24.209],[0.546,2.792],[-1.195,17.217],[-1.162,-12.183]],"v":[[76.183,-27.535],[-75.431,-27.53],[-82.067,39.59],[82.067,41.073]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":156,"s":[{"i":[[0,0],[10.725,-1.741],[1.195,-17.217],[1.162,12.183]],"o":[[-17.284,-5.966],[0.546,2.792],[-1.195,17.217],[-1.162,-12.183]],"v":[[76.183,-27.535],[-75.431,-27.53],[-82.067,39.59],[82.067,41.073]],"c":true}]},{"t":166,"s":[{"i":[[0,0],[2.168,-11.913],[1.195,-17.217],[1.162,12.183]],"o":[[-2.362,-13.992],[0.546,2.792],[-1.195,17.217],[-1.162,-12.183]],"v":[[76.183,-27.535],[-75.431,-27.53],[-82.067,39.59],[82.067,41.073]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.866666666667,0.650980392157,0.16862745098,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.831372559071,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":289,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"hat_t","parent":23,"sr":1,"ks":{"o":{"a":1,"k":[{"t":59,"s":[100],"h":1},{"t":102,"s":[0],"h":1},{"t":156,"s":[100],"h":1}]},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":74,"s":[-3.961]},{"i":{"x":[0.141],"y":[2.413]},"o":{"x":[0.333],"y":[0]},"t":130,"s":[37.971]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":145,"s":[37.971]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":165,"s":[-3.961]},{"t":202,"s":[-3.961]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":74,"s":[-22.126,-164.845,0],"to":[0,0,0],"ti":[-52.331,-9.451,0]},{"t":130,"s":[46.96,-255.218,0],"h":1},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":148,"s":[46.985,-255.23,0],"to":[-57.702,1.424,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":168,"s":[-13.759,-140.335,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.167,"y":0.167},"t":177,"s":[-22.126,-164.845,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":202,"s":[-22.126,-164.845,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":225,"s":[-11.405,-164.716,0],"to":[0,0,0],"ti":[0,0,0]},{"t":238,"s":[-22.126,-164.845,0]}]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":152,"s":[142,142,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":165,"s":[145,138,100]},{"t":173,"s":[142,142,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":74,"s":[{"i":[[-0.966,-11.915],[77.038,-0.149],[0.966,11.915],[-77.038,0.149]],"o":[[0.966,11.915],[-77.038,0.149],[-0.966,-11.915],[77.038,-0.149]],"v":[[139.49,-0.27],[1.748,21.574],[-139.49,0.27],[-1.748,-21.574]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":101,"s":[{"i":[[-0.966,0.05],[77.038,0.001],[0.966,-0.05],[-77.038,-0.001]],"o":[[0.966,-0.05],[-77.038,-0.001],[-0.966,0.05],[77.038,0.001]],"v":[[139.49,0.001],[1.748,-0.091],[-139.49,-0.001],[-1.748,0.091]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":130,"s":[{"i":[[-0.966,12.999],[77.038,0.163],[0.966,-12.999],[-77.038,-0.163]],"o":[[0.966,-12.999],[-77.038,-0.163],[-0.966,12.999],[77.038,0.163]],"v":[[139.49,0.295],[1.748,-23.537],[-139.49,-0.295],[-1.748,23.537]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":145,"s":[{"i":[[-0.966,12.999],[77.038,0.163],[0.966,-12.999],[-77.038,-0.163]],"o":[[0.966,-12.999],[-77.038,-0.163],[-0.966,12.999],[77.038,0.163]],"v":[[139.49,0.295],[1.748,-23.537],[-139.49,-0.295],[-1.748,23.537]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":155,"s":[{"i":[[-0.966,0.05],[77.038,0.001],[0.966,-0.05],[-77.038,-0.001]],"o":[[0.966,-0.05],[-77.038,-0.001],[-0.966,0.05],[77.038,0.001]],"v":[[139.49,0.001],[1.748,-0.091],[-139.49,-0.001],[-1.748,0.091]],"c":true}]},{"t":165,"s":[{"i":[[-0.966,-11.915],[77.038,-0.149],[0.966,11.915],[-77.038,0.149]],"o":[[0.966,11.915],[-77.038,0.149],[-0.966,-11.915],[77.038,-0.149]],"v":[[139.49,-0.27],[1.748,21.574],[-139.49,0.27],[-1.748,-21.574]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.868612132353,0.652802231733,0.170422542796,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.831372559071,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":-1,"op":287,"st":-1,"bm":0},{"ddd":0,"ind":9,"ty":3,"nm":"ALL_SKALE","sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":0,"k":[256.136,487.44,0]},"a":{"a":0,"k":[60,60,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":60,"s":[81,81,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":79,"s":[79.2,82.8,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":89,"s":[82.8,79.2,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":99,"s":[81,81,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":165,"s":[81,81,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":171,"s":[82.8,79.2,100]},{"t":179,"s":[81,81,100]}]}},"ao":0,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"hand_l","parent":23,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[146.844]},{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":81,"s":[-4.787]},{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.413],"y":[0]},"t":106,"s":[28.276]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.413],"y":[0]},"t":142,"s":[28.276]},{"i":{"x":[0.835],"y":[1]},"o":{"x":[0.168],"y":[0]},"t":160,"s":[-4.787]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":176,"s":[-4.787]},{"t":187,"s":[146.844]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":60,"s":[-135.633,69.147,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.167,"y":0},"t":81,"s":[-138.132,56.404,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.58,"y":0.58},"o":{"x":0.413,"y":0.413},"t":106,"s":[-130.262,81.021,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.413,"y":0.413},"t":142,"s":[-130.262,81.021,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.835,"y":1},"o":{"x":0.168,"y":0},"t":160,"s":[-130.262,81.021,0],"to":[0,0,0],"ti":[0,0,0]},{"t":176,"s":[-135.633,69.147,0]}]},"a":{"a":0,"k":[-6.043,-54.406,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":63,"s":[{"i":[[-20.246,-0.164],[-25.837,-42.482]],"o":[[84.347,0.682],[14.221,23.382]],"v":[[-6.043,-54.406],[32.02,25.514]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":84,"s":[{"i":[[5.986,-19.341],[4.658,-31.503]],"o":[[-5.986,19.341],[-4.003,27.072]],"v":[[-6.043,-54.406],[-23.743,48.813]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":109,"s":[{"i":[[1.711,-20.174],[16.653,-10.459]],"o":[[-2.705,31.894],[-23.175,14.555]],"v":[[-6.043,-54.406],[-68.702,41.734]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":145,"s":[{"i":[[1.711,-20.174],[16.653,-10.459]],"o":[[-2.705,31.894],[-23.175,14.555]],"v":[[-6.043,-54.406],[-68.702,41.734]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":163,"s":[{"i":[[1.003,-20.222],[-13.659,-24.822]],"o":[[-1.554,31.346],[13.193,23.977]],"v":[[-6.043,-54.406],[22.877,47.401]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":178,"s":[{"i":[[5.986,-19.341],[4.658,-31.503]],"o":[[-5.986,19.341],[-4.003,27.072]],"v":[[-6.043,-54.406],[-23.743,48.813]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":184,"s":[{"i":[[-20.241,-0.486],[40.03,-21.378]],"o":[[53.9,1.293],[-24.14,12.892]],"v":[[-6.043,-54.406],[9.074,11.655]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":190,"s":[{"i":[[-20.241,-0.486],[40.03,-21.378]],"o":[[53.9,1.293],[-24.14,12.892]],"v":[[-6.043,-54.406],[9.074,11.655]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":196,"s":[{"i":[[-20.246,-0.164],[-25.837,-42.482]],"o":[[84.347,0.682],[14.221,23.382]],"v":[[-6.043,-54.406],[32.02,25.514]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":201,"s":[{"i":[[-20.241,-0.486],[40.03,-21.378]],"o":[[53.9,1.293],[-24.14,12.892]],"v":[[-6.043,-54.406],[9.074,11.655]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":206,"s":[{"i":[[-20.246,-0.164],[-25.837,-42.482]],"o":[[84.347,0.682],[14.221,23.382]],"v":[[-6.043,-54.406],[32.02,25.514]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":212,"s":[{"i":[[-20.241,-0.486],[40.03,-21.378]],"o":[[53.9,1.293],[-24.14,12.892]],"v":[[-6.043,-54.406],[9.074,11.655]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":217,"s":[{"i":[[-20.246,-0.164],[-25.837,-42.482]],"o":[[84.347,0.682],[14.221,23.382]],"v":[[-6.043,-54.406],[32.02,25.514]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":223,"s":[{"i":[[-20.241,-0.486],[40.03,-21.378]],"o":[[53.9,1.293],[-24.14,12.892]],"v":[[-6.043,-54.406],[9.074,11.655]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":228,"s":[{"i":[[-20.246,-0.164],[-25.837,-42.482]],"o":[[84.347,0.682],[14.221,23.382]],"v":[[-6.043,-54.406],[32.02,25.514]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":234,"s":[{"i":[[-20.241,-0.486],[40.03,-21.378]],"o":[[53.9,1.293],[-24.14,12.892]],"v":[[-6.043,-54.406],[9.074,11.655]],"c":false}]},{"t":240,"s":[{"i":[[-20.246,-0.164],[-25.837,-42.482]],"o":[[84.347,0.682],[14.221,23.382]],"v":[[-6.043,-54.406],[32.02,25.514]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.784313738346,0.701960802078,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":20},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"hand_l_shd","parent":23,"sr":1,"ks":{"o":{"a":0,"k":40},"p":{"a":0,"k":[-108.393,90.261,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":60,"s":[{"i":[[-5.11,-19.008],[-17.147,-11.731]],"o":[[5.277,19.627],[0,0]],"v":[[-26.987,-20.859],[19.997,30.214]],"c":false}]},{"i":{"x":0.58,"y":1},"o":{"x":0.167,"y":0},"t":81,"s":[{"i":[[-5.11,-19.008],[-17.147,-11.731]],"o":[[5.277,19.627],[0,0]],"v":[[-26.987,-20.859],[19.997,30.214]],"c":false}]},{"i":{"x":0.58,"y":1},"o":{"x":0.413,"y":0},"t":106,"s":[{"i":[[-5.878,-18.785],[-11.99,-8.107]],"o":[[5.62,17.961],[0,0]],"v":[[-19.506,-6.23],[16.177,34.709]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.413,"y":0},"t":142,"s":[{"i":[[-5.878,-18.785],[-11.99,-8.107]],"o":[[5.62,17.961],[0,0]],"v":[[-19.506,-6.23],[16.177,34.709]],"c":false}]},{"i":{"x":0.835,"y":1},"o":{"x":0.168,"y":0},"t":160,"s":[{"i":[[-5.878,-18.785],[-11.99,-8.107]],"o":[[5.62,17.961],[0,0]],"v":[[-19.506,-6.23],[16.177,34.709]],"c":false}]},{"t":176,"s":[{"i":[[-5.11,-19.008],[-17.147,-11.731]],"o":[[5.277,19.627],[0,0]],"v":[[-26.987,-20.859],[19.997,30.214]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.898039275525,0.619607843137,0.509803921569,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":20},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"hair_bl 2","parent":13,"sr":1,"ks":{"o":{"a":0,"k":44},"r":{"a":0,"k":5.461},"p":{"a":0,"k":[-192.592,-36.468,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.37,"y":1},"o":{"x":0.167,"y":0},"t":60,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[292.49,13.968],[277.475,-12.565]],"c":false}]},{"i":{"x":0.501,"y":1},"o":{"x":0.63,"y":0},"t":120,"s":[{"i":[[0.195,9.466],[-8.241,13.571]],"o":[[-0.379,-18.452],[8.523,-14.036]],"v":[[299.26,11.476],[310.838,-42.561]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.63,"y":0},"t":156,"s":[{"i":[[0.195,9.466],[-8.241,13.571]],"o":[[-0.379,-18.452],[8.523,-14.036]],"v":[[299.26,11.476],[310.838,-42.561]],"c":false}]},{"t":176,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[292.49,13.968],[277.475,-12.565]],"c":false}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.37,"y":1},"o":{"x":0.167,"y":0},"t":60,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[215.774,33.587],[203.128,3.344]],"c":false}]},{"i":{"x":0.501,"y":1},"o":{"x":0.63,"y":0},"t":120,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[215.774,33.587],[203.128,3.344]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.63,"y":0},"t":156,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[215.774,33.587],[203.128,3.344]],"c":false}]},{"t":176,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[215.774,33.587],[203.128,3.344]],"c":false}]}]},"nm":"Path 2","hd":false},{"ty":"st","c":{"a":0,"k":[0.858823529412,0.501960784314,0.341176470588,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":13},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.37],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":60,"s":[0]},{"i":{"x":[0.501],"y":[1]},"o":{"x":[0.63],"y":[0]},"t":88,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.63],"y":[0]},"t":161,"s":[0]},{"t":176,"s":[0]}]},"e":{"a":1,"k":[{"i":{"x":[0.37],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":60,"s":[21]},{"i":{"x":[0.501],"y":[1]},"o":{"x":[0.63],"y":[0]},"t":88,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.63],"y":[0]},"t":161,"s":[100]},{"t":176,"s":[21]}]},"o":{"a":0,"k":0},"m":1,"nm":"Trim Paths 1","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"hair2","parent":23,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.37],"y":[1]},"o":{"x":[0.63],"y":[0]},"t":60,"s":[0]},{"i":{"x":[0.501],"y":[1]},"o":{"x":[0.465],"y":[0]},"t":120,"s":[-20]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.63],"y":[0]},"t":156,"s":[-20]},{"t":176,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.37,"y":1},"o":{"x":0.63,"y":0},"t":60,"s":[-8.324,-115.666,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.501,"y":1},"o":{"x":0.709,"y":0},"t":120,"s":[25.013,-53.517,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.63,"y":0},"t":156,"s":[25.013,-53.517,0],"to":[0,0,0],"ti":[0,0,0]},{"t":176,"s":[-8.324,-115.666,0]}]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.37,"y":1},"o":{"x":0.167,"y":0},"t":60,"s":[{"i":[[0,0],[-82.293,-13.259],[0,0],[-34.507,-8.177],[0,0]],"o":[[19.495,25.054],[-6.704,-24.137],[22.346,11.668],[0.465,-12.358],[0,0]],"v":[[-98.441,-37.98],[38.159,35.769],[24.918,-11.127],[116.84,23.477],[98.441,-29.472]],"c":false}]},{"i":{"x":0.501,"y":1},"o":{"x":0.63,"y":0},"t":120,"s":[{"i":[[0,0],[-82.293,-13.259],[0,0],[-34.507,-8.177],[-21.832,21.441]],"o":[[4.828,52.466],[-6.704,-24.137],[22.346,11.668],[0.465,-12.358],[21.832,-21.441]],"v":[[-82.502,-54.481],[38.159,35.769],[24.918,-11.127],[116.84,23.477],[143.984,-57.363]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.63,"y":0},"t":156,"s":[{"i":[[0,0],[-82.293,-13.259],[0,0],[-34.507,-8.177],[-21.832,21.441]],"o":[[4.828,52.466],[-6.704,-24.137],[22.346,11.668],[0.465,-12.358],[21.832,-21.441]],"v":[[-82.502,-54.481],[38.159,35.769],[24.918,-11.127],[116.84,23.477],[143.984,-57.363]],"c":false}]},{"t":176,"s":[{"i":[[0,0],[-82.293,-13.259],[0,0],[-34.507,-8.177],[0,0]],"o":[[19.495,25.054],[-6.704,-24.137],[22.346,11.668],[0.465,-12.358],[0,0]],"v":[[-98.441,-37.98],[38.159,35.769],[24.918,-11.127],[116.84,23.477],[98.441,-29.472]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.396078431373,0.156862745098,0.192156862745,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":15},"lc":2,"lj":2,"bm":0,"d":[{"n":"d","nm":"dash","v":{"a":0,"k":10}},{"n":"o","nm":"offset","v":{"a":0,"k":0}}],"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.576470588235,0.317647058824,0.356862745098,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"hair2_shd","parent":23,"sr":1,"ks":{"o":{"a":0,"k":25},"p":{"a":0,"k":[-27.411,-85.256,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.37,"y":1},"o":{"x":0.63,"y":0},"t":60,"s":[{"i":[[45.331,-31.142],[-53.146,3.12],[0,0],[0,0],[28.099,11.459]],"o":[[18.141,34.099],[2.526,-14.647],[30.792,12.953],[-7.304,-35.266],[-28.099,-11.459]],"v":[[-109.318,-43.404],[18.743,37.468],[29.946,-10.237],[98.222,18.99],[62.745,-47.77]],"c":true}]},{"i":{"x":0.501,"y":1},"o":{"x":0.63,"y":0},"t":120,"s":[{"i":[[45.331,-31.142],[-56.816,-23.894],[0,0],[0,0],[28.099,11.459]],"o":[[0.987,67.095],[2.526,-14.647],[17.031,34.482],[54.95,-80.464],[-28.099,-11.459]],"v":[[-120.841,-9.275],[-15.964,103.575],[-9.61,54.491],[56.834,116.818],[65.495,18.777]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.63,"y":0},"t":156,"s":[{"i":[[45.331,-31.142],[-56.816,-23.894],[0,0],[0,0],[28.099,11.459]],"o":[[0.987,67.095],[2.526,-14.647],[17.031,34.482],[54.95,-80.464],[-28.099,-11.459]],"v":[[-120.841,-9.275],[-15.964,103.575],[-9.61,54.491],[56.834,116.818],[65.495,18.777]],"c":true}]},{"t":176,"s":[{"i":[[45.331,-31.142],[-53.146,3.12],[0,0],[0,0],[28.099,11.459]],"o":[[18.141,34.099],[2.526,-14.647],[30.792,12.953],[-7.304,-35.266],[-28.099,-11.459]],"v":[[-109.318,-43.404],[18.743,37.468],[29.946,-10.237],[98.222,18.99],[62.745,-47.77]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.576470588235,0.317647058824,0.356862745098,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"mouth","parent":17,"sr":1,"ks":{"p":{"a":0,"k":[16.825,10.34,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.706,"y":0},"t":60,"s":[{"i":[[-6.229,5.137],[8.486,8.839],[-14.414,0.396]],"o":[[-8.049,7.529],[8.479,4.642],[17.407,-0.478]],"v":[[30.605,2.022],[-50.496,4.087],[-10.693,9.694]],"c":true}]},{"i":{"x":0.37,"y":1},"o":{"x":0.333,"y":0},"t":91,"s":[{"i":[[-6.229,5.137],[8.486,8.839],[-14.414,0.396]],"o":[[-8.049,7.529],[8.479,4.642],[17.407,-0.478]],"v":[[30.605,2.022],[-50.496,4.087],[-10.693,9.694]],"c":true}]},{"t":120,"s":[{"i":[[-6.229,5.137],[24.116,21.957],[-14.412,0.474]],"o":[[-15.061,16.959],[8.602,6.069],[26.191,-0.862]],"v":[[30.605,2.022],[-50.496,4.087],[-14.322,18.053]],"c":true}],"h":1},{"i":{"x":0.667,"y":1},"o":{"x":0.63,"y":0},"t":156,"s":[{"i":[[-6.229,5.137],[24.116,21.957],[-14.412,0.474]],"o":[[-15.061,16.959],[8.602,6.069],[26.191,-0.862]],"v":[[30.605,2.022],[-50.496,4.087],[-14.322,18.053]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":176,"s":[{"i":[[-6.229,5.137],[8.486,8.839],[-14.414,0.396]],"o":[[-8.049,7.529],[8.479,4.642],[17.407,-0.478]],"v":[[30.605,2.022],[-50.496,4.087],[-10.693,9.694]],"c":true}]},{"i":{"x":0.13,"y":1},"o":{"x":0.333,"y":0},"t":188,"s":[{"i":[[-6.229,5.137],[8.486,8.839],[-14.414,0.396]],"o":[[-8.049,7.529],[8.479,4.642],[17.407,-0.478]],"v":[[30.605,2.022],[-50.496,4.087],[-10.693,9.694]],"c":true}]},{"i":{"x":0.13,"y":1},"o":{"x":0.87,"y":0},"t":199,"s":[{"i":[[1.728,8.466],[1.564,-4.383],[-9.743,0.259]],"o":[[-1.723,-5.836],[-1.096,7.959],[11.766,-0.313]],"v":[[17.615,3.747],[-37.203,5.1],[-10.77,16.167]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.87,"y":0},"t":210,"s":[{"i":[[2.081,14.448],[1.883,-7.48],[-11.73,0.442]],"o":[[-2.075,-9.96],[-1.32,13.583],[14.166,-0.534]],"v":[[23.39,7.872],[-42.611,10.179],[-10.786,29.066]],"c":true}]},{"t":221,"s":[{"i":[[-6.229,5.137],[8.486,8.839],[-14.414,0.396]],"o":[[-8.049,7.529],[8.479,4.642],[17.407,-0.478]],"v":[[30.605,2.022],[-50.496,4.087],[-10.693,9.694]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.309803932905,0.039215687662,0.172549024224,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":12},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"nose","parent":17,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.667,"y":0.667},"o":{"x":0.706,"y":0.706},"t":60,"s":[13.7,-4.322,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.37,"y":1},"o":{"x":0.333,"y":0},"t":91,"s":[13.7,-4.322,0],"to":[0.364,2.134,0],"ti":[-0.364,-2.134,0]},{"t":120,"s":[15.884,8.481,0],"h":1},{"i":{"x":0.667,"y":1},"o":{"x":0.63,"y":0},"t":156,"s":[15.884,8.481,0],"to":[-0.695,-2.101,0],"ti":[1.807,1.996,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":176,"s":[11.713,-4.126,0],"to":[-1.807,-1.996,0],"ti":[1.111,-0.105,0]},{"i":{"x":0.37,"y":0.37},"o":{"x":0.333,"y":0.333},"t":202,"s":[5.044,-3.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.516,"y":1},"o":{"x":0.266,"y":0},"t":225,"s":[5.044,-3.497,0],"to":[1.443,-0.137,0],"ti":[-1.443,0.137,0]},{"t":238,"s":[13.7,-4.318,0]}]},"a":{"a":0,"k":[249.337,239.25,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,-3.039],[5.118,0],[0,3.039],[-5.118,0]],"o":[[0,3.039],[-5.118,0],[0,-3.039],[5.118,0]],"v":[[9.267,0],[0,5.502],[-9.267,0],[0,-5.502]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.992156863213,0.713725507259,0.584313750267,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[248.36,235.413]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":30},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,-7.32],[10.036,0],[0,7.32],[-10.036,0]],"o":[[0,7.32],[-10.036,0],[0,-7.32],[10.036,0]],"v":[[18.172,0],[0,13.255],[-18.172,0],[0,-13.255]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.309803932905,0.039215687662,0.172549024224,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[249.337,239.25]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"face","parent":23,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.37],"y":[1]},"o":{"x":[0.706],"y":[0]},"t":60,"s":[0]},{"i":{"x":[0.501],"y":[1]},"o":{"x":[0.63],"y":[0]},"t":120,"s":[-18.529]},{"i":{"x":[0.37],"y":[1]},"o":{"x":[0.63],"y":[0]},"t":156,"s":[-18.529]},{"t":202,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.37,"y":1},"o":{"x":0.706,"y":0},"t":60,"s":[-8.64,-2.25,0],"to":[34.245,1.817,0],"ti":[10.059,-20.406,0]},{"t":120,"s":[29.689,51.779,0],"h":1},{"i":{"x":0.667,"y":1},"o":{"x":0.63,"y":0},"t":156,"s":[29.92,52.231,0],"to":[-3.727,-13.689,0],"ti":[16.538,13.405,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":176,"s":[-8.64,-2.25,0],"to":[-3.492,-0.202,0],"ti":[0,0,0]},{"i":{"x":0.37,"y":0.37},"o":{"x":0.333,"y":0.333},"t":202,"s":[-21.171,-1.066,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.516,"y":1},"o":{"x":0.266,"y":0},"t":225,"s":[-21.171,-1.066,0],"to":[0,0,0],"ti":[0,0,0]},{"t":238,"s":[-8.64,-2.25,0]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,-20.201],[24.915,0],[0,20.201],[-24.915,0]],"o":[[0,20.201],[-24.915,0],[0,-20.201],[24.915,0]],"v":[[51.604,0.34],[6.491,36.918],[-38.621,0.34],[6.491,-36.237]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.866666674614,0.568627476692,0.450980395079,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":"eye_l_bl 2","parent":19,"sr":1,"ks":{"p":{"a":0,"k":[0.322,-13.299,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":88,"s":[{"i":[[0,-2.933],[3.403,0],[0,2.933],[-3.403,0]],"o":[[0,2.933],[-3.403,0],[0,-2.933],[3.403,0]],"v":[[6.161,0],[0,5.311],[-6.161,0],[0,-5.311]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":95,"s":[{"i":[[0,-1.001],[1.907,0],[0,1.001],[-1.907,0]],"o":[[0,1.001],[-1.907,0],[0,-1.001],[1.907,0]],"v":[[4.547,25.469],[1.093,27.282],[-2.36,25.469],[1.093,23.656]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":149,"s":[{"i":[[0,-1.001],[1.907,0],[0,1.001],[-1.907,0]],"o":[[0,1.001],[-1.907,0],[0,-1.001],[1.907,0]],"v":[[4.547,25.469],[1.093,27.282],[-2.36,25.469],[1.093,23.656]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":156,"s":[{"i":[[0,-2.933],[3.403,0],[0,2.933],[-3.403,0]],"o":[[0,2.933],[-3.403,0],[0,-2.933],[3.403,0]],"v":[[6.161,0],[0,5.311],[-6.161,0],[0,-5.311]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":184,"s":[{"i":[[0,-2.933],[3.403,0],[0,2.933],[-3.403,0]],"o":[[0,2.933],[-3.403,0],[0,-2.933],[3.403,0]],"v":[[6.161,0],[0,5.311],[-6.161,0],[0,-5.311]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":191,"s":[{"i":[[0,-1.001],[1.907,0],[0,1.001],[-1.907,0]],"o":[[0,1.001],[-1.907,0],[0,-1.001],[1.907,0]],"v":[[2.933,6.232],[-0.52,8.045],[-3.974,6.232],[-0.52,4.419]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":225,"s":[{"i":[[0,-1.001],[1.907,0],[0,1.001],[-1.907,0]],"o":[[0,1.001],[-1.907,0],[0,-1.001],[1.907,0]],"v":[[2.933,6.232],[-0.52,8.045],[-3.974,6.232],[-0.52,4.419]],"c":true}]},{"t":232,"s":[{"i":[[0,-2.933],[3.403,0],[0,2.933],[-3.403,0]],"o":[[0,2.933],[-3.403,0],[0,-2.933],[3.403,0]],"v":[[6.161,0],[0,5.311],[-6.161,0],[0,-5.311]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":88,"s":[1,1,1,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1.105],"y":[0]},"t":95,"s":[0.309803921569,0.039215686275,0.172549019608,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":149,"s":[0.309803932905,0.039215687662,0.172549024224,1]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":156,"s":[1,1,1,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":184,"s":[1,1,1,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":191,"s":[0.309803932905,0.039215687662,0.172549024224,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":225,"s":[0.309803932905,0.039215687662,0.172549024224,1]},{"t":232,"s":[1,1,1,1]}]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":"eye_l 2","parent":17,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.706,"y":0},"t":60,"s":[74.898,-31.272,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.37,"y":1},"o":{"x":0.333,"y":0},"t":91,"s":[68.443,-32.493,0],"to":[0,0,0],"ti":[0,0,0]},{"t":120,"s":[74.898,-31.272,0],"h":1},{"i":{"x":0.667,"y":1},"o":{"x":0.63,"y":0},"t":156,"s":[74.898,-31.272,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"t":176,"s":[81.534,-31.617,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.37,"y":0.37},"o":{"x":0.333,"y":0.333},"t":202,"s":[81.534,-31.617,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.516,"y":1},"o":{"x":0.266,"y":0},"t":225,"s":[81.534,-31.617,0],"to":[0,0,0],"ti":[0,0,0]},{"t":238,"s":[74.898,-31.272,0]}]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.706,0.706,0.706],"y":[0,0,0]},"t":60,"s":[110,100,100]},{"i":{"x":[0.37,0.37,0.37],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":91,"s":[100,100,100]},{"t":120,"s":[110,100,100],"h":1},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.63,0.63,0.63],"y":[0,0,0]},"t":156,"s":[110,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":176,"s":[115,100,100]},{"i":{"x":[0.37,0.37,0.37],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":202,"s":[120,100,100]},{"i":{"x":[0.516,0.516,0.516],"y":[1,1,1]},"o":{"x":[0.266,0.266,0.266],"y":[0,0,0]},"t":225,"s":[115,100,100]},{"t":238,"s":[110,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":88,"s":[{"i":[[10.121,0],[0,13.876],[-10.121,0],[0,-13.876]],"o":[[-10.121,0],[0,-13.876],[10.121,0],[0,13.876]],"v":[[0,25.125],[-18.326,0],[0,-25.125],[18.326,0]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":95,"s":[{"i":[[10.121,0],[-3.284,5.575],[-10.121,0],[-3.89,-6.384]],"o":[[-10.121,0],[2.426,-4.119],[10.121,0],[2.598,4.264]],"v":[[-0.816,15.825],[-18.326,0],[-1.093,8.075],[18.326,0]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":149,"s":[{"i":[[10.121,0],[-3.284,5.575],[-10.121,0],[-3.89,-6.384]],"o":[[-10.121,0],[2.426,-4.119],[10.121,0],[2.598,4.264]],"v":[[-0.816,15.825],[-18.326,0],[-1.093,8.075],[18.326,0]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":156,"s":[{"i":[[10.121,0],[0,13.876],[-10.121,0],[0,-13.876]],"o":[[-10.121,0],[0,-13.876],[10.121,0],[0,13.876]],"v":[[0,25.125],[-18.326,0],[0,-25.125],[18.326,0]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":184,"s":[{"i":[[10.121,0],[0,13.876],[-10.121,0],[0,-13.876]],"o":[[-10.121,0],[0,-13.876],[10.121,0],[0,13.876]],"v":[[0,25.125],[-18.326,0],[0,-25.125],[18.326,0]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":191,"s":[{"i":[[10.121,0],[0.331,4.518],[-10.121,0],[0.437,-4.597]],"o":[[-10.121,0],[-0.187,-2.557],[10.121,0],[-0.472,4.97]],"v":[[-0.645,-3.264],[-18.326,0],[-0.922,-11.014],[18.326,0]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":225,"s":[{"i":[[10.121,0],[0.331,4.518],[-10.121,0],[0.437,-4.597]],"o":[[-10.121,0],[-0.187,-2.557],[10.121,0],[-0.472,4.97]],"v":[[-0.645,-3.264],[-18.326,0],[-0.922,-11.014],[18.326,0]],"c":true}]},{"t":232,"s":[{"i":[[10.121,0],[0,13.876],[-10.121,0],[0,-13.876]],"o":[[-10.121,0],[0,-13.876],[10.121,0],[0,13.876]],"v":[[0,25.125],[-18.326,0],[0,-25.125],[18.326,0]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.309803932905,0.039215687662,0.172549024224,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"st","c":{"a":0,"k":[0.309803932905,0.039215687662,0.172549024224,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":88,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.308],"y":[0]},"t":95,"s":[6]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":149,"s":[6]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":156,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":184,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.308],"y":[0]},"t":191,"s":[9]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.308],"y":[0]},"t":225,"s":[9]},{"t":232,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":"eye_l_bl","parent":21,"sr":1,"ks":{"p":{"a":0,"k":[0.322,-13.299,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":88,"s":[{"i":[[0,-2.933],[3.403,0],[0,2.933],[-3.403,0]],"o":[[0,2.933],[-3.403,0],[0,-2.933],[3.403,0]],"v":[[6.161,0],[0,5.311],[-6.161,0],[0,-5.311]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":95,"s":[{"i":[[0,-1.001],[1.907,0],[0,1.001],[-1.907,0]],"o":[[0,1.001],[-1.907,0],[0,-1.001],[1.907,0]],"v":[[4.547,25.469],[1.093,27.282],[-2.36,25.469],[1.093,23.656]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":149,"s":[{"i":[[0,-1.001],[1.907,0],[0,1.001],[-1.907,0]],"o":[[0,1.001],[-1.907,0],[0,-1.001],[1.907,0]],"v":[[4.547,25.469],[1.093,27.282],[-2.36,25.469],[1.093,23.656]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":156,"s":[{"i":[[0,-2.933],[3.403,0],[0,2.933],[-3.403,0]],"o":[[0,2.933],[-3.403,0],[0,-2.933],[3.403,0]],"v":[[6.161,0],[0,5.311],[-6.161,0],[0,-5.311]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":184,"s":[{"i":[[0,-2.933],[3.403,0],[0,2.933],[-3.403,0]],"o":[[0,2.933],[-3.403,0],[0,-2.933],[3.403,0]],"v":[[6.161,0],[0,5.311],[-6.161,0],[0,-5.311]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":191,"s":[{"i":[[0,-1.001],[1.907,0],[0,1.001],[-1.907,0]],"o":[[0,1.001],[-1.907,0],[0,-1.001],[1.907,0]],"v":[[2.933,6.232],[-0.52,8.045],[-3.974,6.232],[-0.52,4.419]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":225,"s":[{"i":[[0,-1.001],[1.907,0],[0,1.001],[-1.907,0]],"o":[[0,1.001],[-1.907,0],[0,-1.001],[1.907,0]],"v":[[2.933,6.232],[-0.52,8.045],[-3.974,6.232],[-0.52,4.419]],"c":true}]},{"t":232,"s":[{"i":[[0,-2.933],[3.403,0],[0,2.933],[-3.403,0]],"o":[[0,2.933],[-3.403,0],[0,-2.933],[3.403,0]],"v":[[6.161,0],[0,5.311],[-6.161,0],[0,-5.311]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":88,"s":[1,1,1,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1.105],"y":[0]},"t":95,"s":[0.309803921569,0.039215686275,0.172549019608,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":149,"s":[0.309803932905,0.039215687662,0.172549024224,1]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":156,"s":[1,1,1,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":184,"s":[1,1,1,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":191,"s":[0.309803932905,0.039215687662,0.172549024224,1]},{"i":{"x":[0],"y":[1]},"o":{"x":[1],"y":[0]},"t":225,"s":[0.309803932905,0.039215687662,0.172549024224,1]},{"t":232,"s":[1,1,1,1]}]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":"eye_l","parent":17,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"t":176,"s":[-68.367,-22.554,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.37,"y":0.37},"o":{"x":0.333,"y":0.333},"t":202,"s":[-68.367,-22.554,0],"to":[0,0,0],"ti":[0,0,0]},{"t":225,"s":[-68.367,-22.554,0]}]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":176,"s":[120,100,100]},{"i":{"x":[0.37,0.37,0.37],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":202,"s":[110,100,100]},{"t":225,"s":[120,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":88,"s":[{"i":[[10.121,0],[0,13.876],[-10.121,0],[0,-13.876]],"o":[[-10.121,0],[0,-13.876],[10.121,0],[0,13.876]],"v":[[0,25.125],[-18.326,0],[0,-25.125],[18.326,0]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":95,"s":[{"i":[[10.121,0],[-3.284,5.575],[-10.121,0],[-3.89,-6.384]],"o":[[-10.121,0],[2.426,-4.119],[10.121,0],[2.598,4.264]],"v":[[-0.816,15.825],[-18.326,0],[-1.093,8.075],[18.326,0]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":149,"s":[{"i":[[10.121,0],[-3.284,5.575],[-10.121,0],[-3.89,-6.384]],"o":[[-10.121,0],[2.426,-4.119],[10.121,0],[2.598,4.264]],"v":[[-0.816,15.825],[-18.326,0],[-1.093,8.075],[18.326,0]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":156,"s":[{"i":[[10.121,0],[0,13.876],[-10.121,0],[0,-13.876]],"o":[[-10.121,0],[0,-13.876],[10.121,0],[0,13.876]],"v":[[0,25.125],[-18.326,0],[0,-25.125],[18.326,0]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":184,"s":[{"i":[[10.121,0],[0,13.876],[-10.121,0],[0,-13.876]],"o":[[-10.121,0],[0,-13.876],[10.121,0],[0,13.876]],"v":[[0,25.125],[-18.326,0],[0,-25.125],[18.326,0]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":191,"s":[{"i":[[10.121,0],[0.331,4.518],[-10.121,0],[0.437,-4.597]],"o":[[-10.121,0],[-0.187,-2.557],[10.121,0],[-0.472,4.97]],"v":[[-0.645,-3.264],[-18.326,0],[-0.922,-11.014],[18.326,0]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":225,"s":[{"i":[[10.121,0],[0.331,4.518],[-10.121,0],[0.437,-4.597]],"o":[[-10.121,0],[-0.187,-2.557],[10.121,0],[-0.472,4.97]],"v":[[-0.645,-3.264],[-18.326,0],[-0.922,-11.014],[18.326,0]],"c":true}]},{"t":232,"s":[{"i":[[10.121,0],[0,13.876],[-10.121,0],[0,-13.876]],"o":[[-10.121,0],[0,-13.876],[10.121,0],[0,13.876]],"v":[[0,25.125],[-18.326,0],[0,-25.125],[18.326,0]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.309803932905,0.039215687662,0.172549024224,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"st","c":{"a":0,"k":[0.309803932905,0.039215687662,0.172549024224,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":88,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.308],"y":[0]},"t":95,"s":[6]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":149,"s":[6]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":156,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":184,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.308],"y":[0]},"t":191,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.308],"y":[0]},"t":225,"s":[10]},{"t":232,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":"head_bl 2","parent":23,"sr":1,"ks":{"r":{"a":0,"k":2.883},"p":{"a":0,"k":[-131.293,9.257,0]},"a":{"a":0,"k":[-160.25,-87.873,0]},"s":{"a":0,"k":[108.543,104.308,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.37,"y":1},"o":{"x":0.63,"y":0},"t":60,"s":[{"i":[[0,0],[-6.282,22.107]],"o":[[-2.3,-29.201],[0,0]],"v":[[-162.766,-77.131],[-160.084,-151.156]],"c":false}]},{"i":{"x":0.37,"y":1},"o":{"x":0.63,"y":0},"t":120,"s":[{"i":[[0,0],[-5.198,22.387]],"o":[[-3.719,-29.054],[0,0]],"v":[[-158.291,-50.839],[-159.216,-124.907]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.63,"y":0},"t":146,"s":[{"i":[[0,0],[-5.198,22.387]],"o":[[-3.719,-29.054],[0,0]],"v":[[-158.291,-50.839],[-159.216,-124.907]],"c":false}]},{"t":185,"s":[{"i":[[0,0],[-6.282,22.107]],"o":[[-2.3,-29.201],[0,0]],"v":[[-162.766,-77.131],[-160.084,-151.156]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.859420955882,0.822748161765,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":15},"lc":2,"lj":2,"bm":0,"d":[{"n":"d","nm":"dash","v":{"a":0,"k":50}},{"n":"g","nm":"gap","v":{"a":0,"k":20}},{"n":"o","nm":"offset","v":{"a":0,"k":0}}],"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":"body","parent":9,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":62,"s":[3]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":80,"s":[-8.899]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":120,"s":[27.265]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":148,"s":[27.265]},{"i":{"x":[0.667],"y":[0.726]},"o":{"x":[0.333],"y":[0]},"t":187,"s":[6.682]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[-0.329]},"t":219,"s":[-1]},{"t":239,"s":[3]}]},"p":{"a":1,"k":[{"i":{"x":0.37,"y":1},"o":{"x":0.167,"y":0},"t":60,"s":[73.963,-36.799,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.37,"y":1},"o":{"x":0.709,"y":0},"t":95,"s":[9.271,-36.799,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.63,"y":0},"t":146,"s":[9.271,-36.799,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.621,"y":1},"o":{"x":0.333,"y":0},"t":185,"s":[73.79,-36.799,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.621,"y":1},"o":{"x":0.167,"y":0},"t":215,"s":[49.271,-36.799,0],"to":[0,0,0],"ti":[0,0,0]},{"t":236,"s":[73.963,-36.799,0]}]},"a":{"a":0,"k":[0,144.412,0]},"s":{"a":0,"k":[92.12,95.88,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.37,"y":1},"o":{"x":0.63,"y":0},"t":60,"s":[{"i":[[-63.433,0],[-22.906,-74.005],[156.821,0],[-19.858,69.514]],"o":[[76.649,0],[22.906,74.005],[-156.821,0],[19.858,-69.514]],"v":[[-12.772,-144.055],[124.666,-54.191],[-10.129,144.919],[-148.044,-52.516]],"c":true}]},{"i":{"x":0.37,"y":1},"o":{"x":0.63,"y":0},"t":120,"s":[{"i":[[-58.459,24.624],[-48.741,-70.087],[148.256,-40.322],[-13.943,59.414]],"o":[[57.125,-24.062],[44.231,63.602],[-151.324,41.156],[16.518,-70.382]],"v":[[-7.154,-74.134],[114.467,-54.935],[18.121,152.051],[-147.744,-15.334]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.63,"y":0},"t":146,"s":[{"i":[[-58.459,24.624],[-48.741,-70.087],[148.256,-40.322],[-13.943,59.414]],"o":[[57.125,-24.062],[44.231,63.602],[-151.324,41.156],[16.518,-70.382]],"v":[[-7.154,-74.134],[114.467,-54.935],[18.121,152.051],[-147.744,-15.334]],"c":true}]},{"i":{"x":0.37,"y":1},"o":{"x":0.333,"y":0},"t":185,"s":[{"i":[[-63.433,0],[-22.906,-74.005],[156.821,0],[-19.858,69.514]],"o":[[76.649,0],[22.906,74.005],[-156.821,0],[19.858,-69.514]],"v":[[-12.772,-144.055],[124.666,-54.191],[-10.129,144.919],[-148.044,-52.516]],"c":true}]},{"t":215,"s":[{"i":[[-63.433,0],[-22.906,-74.005],[156.821,0],[-19.858,69.514]],"o":[[76.649,0],[22.906,74.005],[-156.821,0],[19.858,-69.514]],"v":[[-12.772,-144.055],[124.666,-54.191],[-10.129,144.919],[-148.044,-52.516]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,0.784,0.702,0.5,0.933,0.676,0.576,1,0.867,0.569,0.451]}},"s":{"a":0,"k":[-0.354,49.872]},"e":{"a":0,"k":[-0.354,135.165]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":24,"ty":4,"nm":"leg_r","parent":9,"sr":1,"ks":{"p":{"a":0,"k":[103.516,60,0]},"a":{"a":0,"k":[-6.618,51.641,0]},"s":{"a":0,"k":[94,94,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.37,"y":1},"o":{"x":0.167,"y":0},"t":60,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-9.132,-79.977],[-4.995,45.551],[4.995,51.641]],"c":false}]},{"i":{"x":0.37,"y":1},"o":{"x":0.709,"y":0},"t":95,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-72.242,-81.153],[-4.995,45.551],[4.995,51.641]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.63,"y":0},"t":146,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-65.186,-75.656],[-4.995,45.551],[4.995,51.641]],"c":false}]},{"i":{"x":0.621,"y":1},"o":{"x":0.333,"y":0},"t":185,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4.494,-79.321],[-4.995,45.551],[4.995,51.641]],"c":false}]},{"i":{"x":0.621,"y":1},"o":{"x":0.167,"y":0},"t":215,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-40.653,-79.977],[-4.995,45.551],[4.995,51.641]],"c":false}]},{"t":236,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-9.132,-79.977],[-4.995,45.551],[4.995,51.641]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.866666674614,0.568627476692,0.450980395079,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":20},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":25,"ty":4,"nm":"leg_l","parent":9,"sr":1,"ks":{"p":{"a":0,"k":[16.484,60,0]},"a":{"a":0,"k":[0.764,51.641,0]},"s":{"a":0,"k":[94,94,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.37,"y":1},"o":{"x":0.167,"y":0},"t":60,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[16.543,-58.024],[6.068,45.551],[-6.068,51.641]],"c":false}]},{"i":{"x":0.37,"y":1},"o":{"x":0.709,"y":0},"t":95,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-30.634,-71.854],[6.068,45.551],[-6.068,51.641]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.63,"y":0},"t":146,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-30.634,-71.854],[6.068,45.551],[-6.068,51.641]],"c":false}]},{"i":{"x":0.621,"y":1},"o":{"x":0.333,"y":0},"t":185,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[29.493,-58.024],[6.068,45.551],[-6.068,51.641]],"c":false}]},{"i":{"x":0.621,"y":1},"o":{"x":0.167,"y":0},"t":215,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-9.725,-58.024],[6.068,45.551],[-6.068,51.641]],"c":false}]},{"t":236,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[16.543,-58.024],[6.068,45.551],[-6.068,51.641]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.866666674614,0.568627476692,0.450980395079,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":20},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":26,"ty":4,"nm":"hand_r","parent":23,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[-13.673]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":75,"s":[-168.873]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":105,"s":[-135.411]},{"i":{"x":[0.175],"y":[1.008]},"o":{"x":[0.333],"y":[0]},"t":146,"s":[-135.411]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":166,"s":[-168.873]},{"t":178,"s":[-13.673]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":60,"s":[114.202,9.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":75,"s":[113.466,-31.332,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":166,"s":[113.466,-31.332,0],"to":[0,0,0],"ti":[0,0,0]},{"t":178,"s":[114.202,9.222,0]}]},"a":{"a":0,"k":[-10.419,-48.387,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":60,"s":[{"i":[[0,0],[-3.272,-40.846]],"o":[[-9.914,43.85],[-4.241,11.341]],"v":[[-10.523,-48.561],[-22.803,80.6]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":75,"s":[{"i":[[0,0],[-41.107,-24.076]],"o":[[-77.807,54.677],[-4.241,11.341]],"v":[[-10.523,-48.561],[-12.305,88.908]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":91,"s":[{"i":[[0,0],[-47.167,-11.272]],"o":[[-41.163,57.169],[-4.241,11.341]],"v":[[-10.523,-48.561],[15.365,93.495]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":105,"s":[{"i":[[0,0],[-25.613,-31.44]],"o":[[-18.998,63.335],[-4.241,11.341]],"v":[[-10.523,-48.561],[22.447,95.936]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":131,"s":[{"i":[[0,0],[-23.369,-38.821]],"o":[[-24.1,51.417],[-4.241,11.341]],"v":[[-10.523,-48.561],[-1.754,91.042]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":146,"s":[{"i":[[0,0],[-23.369,-38.821]],"o":[[-24.1,51.417],[-4.241,11.341]],"v":[[-10.523,-48.561],[-1.754,91.042]],"c":false}]},{"i":{"x":0.175,"y":0.175},"o":{"x":0.167,"y":0.167},"t":156,"s":[{"i":[[0,0],[-32.238,-31.449]],"o":[[-35.401,40.797],[-4.241,11.341]],"v":[[-10.523,-48.561],[-48.945,111.4]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":166,"s":[{"i":[[0,0],[-41.107,-24.076]],"o":[[-46.702,30.177],[-4.241,11.341]],"v":[[-10.523,-48.561],[-53.85,83.556]],"c":false}]},{"t":178,"s":[{"i":[[0,0],[17.119,-46.344]],"o":[[12.297,27.477],[-4.241,11.341]],"v":[[-10.523,-48.561],[-12.305,88.908]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.933333393172,0.678431372549,0.580392156863,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":20},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":27,"ty":4,"nm":"hair_bl","parent":28,"sr":1,"ks":{"o":{"a":0,"k":44},"p":{"a":0,"k":[-44.09,-42.037,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[206.729,170.081],[183.272,88.476],[250.903,95.763],[205.936,25.462],[254.24,7.588],[195.211,-41.346],[226.955,-71.937],[164.637,-100.615],[168.947,-134.892],[109.496,-137.529],[88.228,-172.824]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.858823529412,0.501960784314,0.341176470588,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":13},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":1,"k":[{"i":{"x":[0.37],"y":[1]},"o":{"x":[0.706],"y":[0]},"t":64,"s":[64]},{"t":100,"s":[100],"h":1},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.63],"y":[0]},"t":156,"s":[100]},{"t":162,"s":[64]}]},"o":{"a":0,"k":0},"m":1,"nm":"Trim Paths 1","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":28,"ty":4,"nm":"hair1","parent":23,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":48,"s":[-5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":68,"s":[-5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":71,"s":[-5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":89,"s":[-10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":129,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":157,"s":[-5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":196,"s":[-10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":228,"s":[-5]},{"t":248,"s":[-5]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":0.667},"o":{"x":0.63,"y":0.63},"t":156,"s":[-14.561,-5.151,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":176,"s":[-14.561,-5.151,0],"to":[1.556,-0.147,0],"ti":[-1.556,0.147,0]},{"i":{"x":0.37,"y":0.37},"o":{"x":0.333,"y":0.333},"t":193,"s":[-5.225,-6.033,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.516,"y":1},"o":{"x":0.266,"y":0},"t":225,"s":[-5.225,-6.033,0],"to":[-1.556,0.147,0],"ti":[1.556,-0.147,0]},{"t":238,"s":[-14.561,-5.151,0]}]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":78,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[24.061,-182.722],[56.629,-181.785],[70.317,-182.976],[111.248,-170.82],[135.624,-149.846],[203.324,-118.94],[173.568,-84.126],[238.365,-28.758],[181.5,-8.654],[237.517,71.487],[158.05,63.52],[188.186,156.93],[107.271,119.916],[108.367,214.922],[37.944,150.782],[11.862,235.435],[-37.944,150.782],[-84.644,214.922],[-107.271,119.916],[-164.463,156.93],[-158.05,63.52],[-213.794,71.487],[-181.5,-8.654],[-224.107,-26.634],[-173.568,-84.126],[-205.265,-125.344],[-135.624,-149.846],[-108.365,-153.253],[-61.545,-160.917],[-19.035,-172.35]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":90,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[24.061,-182.722],[60.541,-193.261],[74.23,-194.452],[141.12,-191.15],[135.624,-149.846],[203.324,-118.94],[173.568,-84.126],[238.365,-28.758],[181.5,-8.654],[237.517,71.487],[158.05,63.52],[188.186,156.93],[107.271,119.916],[108.367,214.922],[37.944,150.782],[11.862,235.435],[-37.944,150.782],[-84.644,214.922],[-107.271,119.916],[-164.463,156.93],[-158.05,63.52],[-213.794,71.487],[-181.5,-8.654],[-224.107,-26.634],[-173.568,-84.126],[-205.265,-125.344],[-135.624,-149.846],[-108.365,-153.253],[-61.545,-160.917],[-19.035,-172.35]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":103,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[0,-210.23],[50.915,-239.926],[74.23,-194.452],[141.12,-191.15],[135.624,-149.846],[203.324,-118.94],[173.568,-84.126],[238.365,-28.758],[181.5,-8.654],[237.517,71.487],[158.05,63.52],[188.186,156.93],[107.271,119.916],[108.367,214.922],[37.944,150.782],[11.862,235.435],[-37.944,150.782],[-84.644,214.922],[-107.271,119.916],[-164.463,156.93],[-158.05,63.52],[-213.794,71.487],[-181.5,-8.654],[-224.107,-26.634],[-173.568,-84.126],[-205.265,-125.344],[-135.624,-149.846],[-134.22,-191.155],[-74.229,-194.452],[-46.913,-239.092]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":114,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[0,-210.23],[52.693,-231.976],[74.23,-194.452],[141.12,-191.15],[135.624,-149.846],[203.324,-118.94],[173.568,-84.126],[238.365,-28.758],[181.5,-8.654],[237.517,71.487],[158.05,63.52],[188.186,156.93],[107.271,119.916],[108.367,214.922],[37.944,150.782],[11.862,235.435],[-37.944,150.782],[-84.644,214.922],[-107.271,119.916],[-164.463,156.93],[-158.05,63.52],[-213.794,71.487],[-181.5,-8.654],[-224.107,-26.634],[-173.568,-84.126],[-205.265,-125.344],[-135.624,-149.846],[-134.22,-191.155],[-74.229,-194.452],[-45.135,-231.142]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":156,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[0,-210.23],[52.693,-231.976],[74.23,-194.452],[141.12,-191.15],[135.624,-149.846],[203.324,-118.94],[173.568,-84.126],[238.365,-28.758],[181.5,-8.654],[237.517,71.487],[158.05,63.52],[188.186,156.93],[107.271,119.916],[108.367,214.922],[37.944,150.782],[11.862,235.435],[-37.944,150.782],[-84.644,214.922],[-107.271,119.916],[-164.463,156.93],[-158.05,63.52],[-213.794,71.487],[-181.5,-8.654],[-224.107,-26.634],[-173.568,-84.126],[-205.265,-125.344],[-135.624,-149.846],[-134.22,-191.155],[-74.229,-194.452],[-45.135,-231.142]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":167,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[13.829,-194.42],[55.032,-205.723],[73.154,-189.649],[111.191,-162.773],[135.624,-149.846],[203.324,-118.94],[173.568,-84.126],[238.365,-28.758],[181.5,-8.654],[237.517,71.487],[158.05,63.52],[188.186,156.93],[107.271,119.916],[108.367,214.922],[37.944,150.782],[11.862,235.435],[-37.944,150.782],[-84.644,214.922],[-107.271,119.916],[-164.463,156.93],[-158.05,63.52],[-213.794,71.487],[-181.5,-8.654],[-224.107,-26.634],[-173.568,-84.126],[-205.265,-125.344],[-135.624,-149.846],[-119.36,-169.371],[-66.939,-175.178],[-30.134,-197.351]],"c":true}]},{"t":176,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[24.061,-182.722],[56.763,-186.299],[72.359,-186.095],[126.675,-173.67],[135.624,-149.846],[203.324,-118.94],[173.568,-84.126],[238.365,-28.758],[181.5,-8.654],[237.517,71.487],[158.05,63.52],[188.186,156.93],[107.271,119.916],[108.367,214.922],[37.944,150.782],[11.862,235.435],[-37.944,150.782],[-84.644,214.922],[-107.271,119.916],[-164.463,156.93],[-158.05,63.52],[-213.794,71.487],[-181.5,-8.654],[-224.107,-26.634],[-173.568,-84.126],[-205.265,-125.344],[-135.624,-149.846],[-108.365,-153.253],[-61.545,-160.917],[-19.035,-172.35]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.396078431373,0.156862745098,0.192156862745,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":15},"lc":2,"lj":2,"bm":0,"d":[{"n":"d","nm":"dash","v":{"a":0,"k":10}},{"n":"o","nm":"offset","v":{"a":0,"k":0}}],"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.576470588235,0.317647058824,0.356862745098,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":29,"ty":4,"nm":"SHD","sr":1,"ks":{"o":{"a":0,"k":10},"p":{"a":0,"k":[256.455,481.455,0]},"a":{"a":0,"k":[-50.545,-56.545,0]},"s":{"a":0,"k":[100,30,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[204.91,204.91]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0,0,0,0.5,0,0,0,0.999,0,0,0,0,1,0.066,1,0.33,1,0.665,0.5,1,0]}},"s":{"a":0,"k":[0,0]},"e":{"a":0,"k":[100,0]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"ggg","hd":false},{"ty":"tr","p":{"a":0,"k":[-50.545,-56.545]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":300,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"10_HELLO","parent":2,"refId":"comp_0","sr":1,"ks":{"p":{"a":0,"k":[256,256,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":124,"op":304,"st":64,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"10_HELLO","refId":"comp_0","sr":1,"ks":{"p":{"a":0,"k":[256,256,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":512,"ip":-56,"op":124,"st":-116,"bm":0}]} diff --git a/frontend/public/star-hello.json b/frontend/public/star-hello.json new file mode 100644 index 0000000..796845e --- /dev/null +++ b/frontend/public/star-hello.json @@ -0,0 +1 @@ +{"tgs":1,"v":"5.5.2","fr":60,"ip":0,"op":180,"w":512,"h":512,"nm":"STAR_05","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 18","parent":18,"sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.57,"y":1},"o":{"x":0.352,"y":0},"t":0,"s":[2.5,55,0],"to":[0.376,0,0],"ti":[-1.25,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.352,"y":0},"t":10,"s":[1,55,0],"to":[0.376,0,0],"ti":[1.456,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[5.023,55,0],"to":[-2.083,0,0],"ti":[-0.839,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[-5.04,55,0],"to":[0.839,0,0],"ti":[0.006,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":34,"s":[10.059,55,0],"to":[-0.006,0,0],"ti":[0.813,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":41,"s":[-5.076,55,0],"to":[-0.813,0,0],"ti":[0.003,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":49,"s":[5.178,55,0],"to":[-0.003,0,0],"ti":[0.446,0,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0.167},"t":59,"s":[-5.096,55,0],"to":[-0.446,0,0],"ti":[-1.266,0,0]},{"i":{"x":0.57,"y":0.57},"o":{"x":0.167,"y":0.167},"t":69,"s":[2.5,55,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0},"t":89,"s":[2.5,55,0],"to":[-0.25,0,0],"ti":[-1.25,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.352,"y":0},"t":100,"s":[1,55,0],"to":[0.376,0,0],"ti":[1.456,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":110,"s":[5.023,55,0],"to":[-2.083,0,0],"ti":[-0.839,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":117,"s":[-5.04,55,0],"to":[0.839,0,0],"ti":[0.006,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[10.059,55,0],"to":[-0.006,0,0],"ti":[0.813,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":131,"s":[-5.076,55,0],"to":[-0.813,0,0],"ti":[0.003,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":139,"s":[5.178,55,0],"to":[-0.003,0,0],"ti":[0.446,0,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0.167},"t":149,"s":[-5.096,55,0],"to":[-0.446,0,0],"ti":[-1.266,0,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0},"t":159,"s":[2.5,55,0],"to":[0,0,0],"ti":[-1.25,0,0]},{"t":179,"s":[2.5,55,0]}]},"a":{"a":0,"k":[50,50,0]}},"ao":0,"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 3","parent":19,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.57],"y":[1]},"o":{"x":[0.42],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.42],"y":[0]},"t":10,"s":[-2]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[9]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":27,"s":[-1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":34,"s":[11]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":41,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":49,"s":[9]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":59,"s":[5]},{"i":{"x":[0.57],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":69,"s":[38]},{"i":{"x":[0.57],"y":[1]},"o":{"x":[0.42],"y":[0]},"t":78,"s":[17]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[24]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":100,"s":[20]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":110,"s":[25]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":117,"s":[15]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":124,"s":[25]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":131,"s":[12]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":139,"s":[18]},{"i":{"x":[0.57],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":149,"s":[7]},{"i":{"x":[0.57],"y":[1]},"o":{"x":[0.42],"y":[0]},"t":159,"s":[12]},{"i":{"x":[0.57],"y":[1]},"o":{"x":[0.42],"y":[0]},"t":168,"s":[-2]},{"t":179,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":0,"s":[56.75,137.649,0],"to":[0.25,-8.5,0],"ti":[-0.167,-5.833,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":60,"s":[58.25,86.649,0],"to":[0.167,5.833,0],"ti":[0,-10.417,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":69,"s":[57.75,172.649,0],"to":[0,10.417,0],"ti":[-0.083,3.5,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":78,"s":[58.25,149.149,0],"to":[0.083,-3.5,0],"ti":[0,-0.417,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.206,"y":0.206},"t":87,"s":[58.25,151.649,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":90,"s":[58.25,151.649,0],"to":[0,-10.833,0],"ti":[0.25,0.667,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":150,"s":[58.25,86.649,0],"to":[-0.25,-0.667,0],"ti":[0.25,-8,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":159,"s":[56.75,147.649,0],"to":[-0.25,8,0],"ti":[0,1.667,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":168,"s":[56.75,134.649,0],"to":[0,-1.667,0],"ti":[0,-0.5,0]},{"t":179,"s":[56.75,137.649,0]}]},"a":{"a":0,"k":[52.25,151,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-7.128,23.854],[-3.548,1.183],[-3.898,-8.835],[0,0]],"o":[[0,0],[2.469,-8.264],[3.93,-1.31],[9.49,21.511],[0,0]],"v":[[28,151],[38.003,84.449],[47,69],[58.97,82.292],[76.5,143.5]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.662745098039,0.282352941176,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.898039275525,0.400000029919,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 2","parent":19,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.57],"y":[1]},"o":{"x":[0.41],"y":[0]},"t":0,"s":[-27]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.41],"y":[0]},"t":10,"s":[-21.667]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[-16.667]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":27,"s":[-28.333]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":34,"s":[-9.263]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":41,"s":[-15.423]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":49,"s":[-0.824]},{"i":{"x":[0.57],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":59,"s":[-8]},{"i":{"x":[0.57],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":72,"s":[8]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":91,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":110,"s":[5]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":117,"s":[-6]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":124,"s":[1]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":131,"s":[-13]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":139,"s":[-3]},{"i":{"x":[0.57],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":149,"s":[-13]},{"i":{"x":[0.57],"y":[1]},"o":{"x":[0.41],"y":[0]},"t":160,"s":[-42]},{"i":{"x":[0.57],"y":[1]},"o":{"x":[0.41],"y":[0]},"t":170,"s":[-23]},{"t":179,"s":[-27]}]},"p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":0,"s":[-49.5,147.149,0],"to":[0,-10.833,0],"ti":[0,-1.667,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":60,"s":[-49.5,82.149,0],"to":[0,1.667,0],"ti":[0,-10.417,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":69,"s":[-49.5,157.149,0],"to":[0,10.417,0],"ti":[0,1.667,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":78,"s":[-49.5,144.649,0],"to":[0,-1.667,0],"ti":[0,-0.417,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.206,"y":0.206},"t":87,"s":[-49.5,147.149,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":90,"s":[-49.5,147.149,0],"to":[0,-10.833,0],"ti":[0,-1.667,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":150,"s":[-49.5,82.149,0],"to":[0,1.667,0],"ti":[0,-10.417,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":159,"s":[-49.5,157.149,0],"to":[0,10.417,0],"ti":[0,1.667,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":168,"s":[-49.5,144.649,0],"to":[0,-1.667,0],"ti":[0,-0.417,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.206,"y":0.206},"t":177,"s":[-49.5,147.149,0],"to":[0,0,0],"ti":[0,0,0]},{"t":180,"s":[-49.5,147.149,0]}]},"a":{"a":0,"k":[-64.5,146.5,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-9.12,21.812],[-3.542,0.236],[-2.753,-8.671],[0,0]],"o":[[0,0],[3.251,-7.776],[4.075,-0.272],[7.381,23.248],[0,0]],"v":[[-88,141.5],[-71.804,78.873],[-61.5,65.5],[-51.298,79.753],[-41,146.5]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.662745098039,0.282352941176,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.898039275525,0.400000029919,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 19","parent":6,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.57,"y":1},"o":{"x":0.41,"y":0},"t":0,"s":[54.503,80.92,0],"to":[0,-1.994,0],"ti":[0,0,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.41,"y":0},"t":60,"s":[54.503,68.958,0],"to":[0,0,0],"ti":[0,-1.994,0]},{"i":{"x":0.57,"y":0.57},"o":{"x":0.41,"y":0.41},"t":70,"s":[54.503,80.92,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.417,"y":0},"t":90,"s":[54.503,80.92,0],"to":[0,-0.925,0],"ti":[0,0,0]},{"i":{"x":0.577,"y":1},"o":{"x":0.417,"y":0},"t":120,"s":[54.503,80.92,0],"to":[0,-0.925,0],"ti":[0,0,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.41,"y":0},"t":150,"s":[54.503,68.958,0],"to":[0,0,0],"ti":[0,-1.994,0]},{"t":160,"s":[54.503,80.92,0]}]},"a":{"a":0,"k":[-68.81,51.101,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[3.013,-15.497],[0.5,14]],"o":[[-3.5,18],[-0.412,-11.536]],"v":[[-103,49.5],[-34.5,51]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.090196078431,0.321568627451,0.298039215686,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.530532418045,0.647307631549,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 18","parent":7,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.57,"y":1},"o":{"x":0.41,"y":0},"t":0,"s":[53.503,81.92,0],"to":[0,-1.994,0],"ti":[0,0,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.41,"y":0},"t":60,"s":[53.503,69.958,0],"to":[0,0,0],"ti":[0,-1.994,0]},{"i":{"x":0.57,"y":0.57},"o":{"x":0.41,"y":0.41},"t":70,"s":[53.503,81.92,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.417,"y":0},"t":90,"s":[53.503,81.92,0],"to":[0,-0.925,0],"ti":[0,0,0]},{"i":{"x":0.577,"y":1},"o":{"x":0.417,"y":0},"t":120,"s":[53.503,81.92,0],"to":[0,-0.925,0],"ti":[0,0,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.41,"y":0},"t":150,"s":[53.503,69.958,0],"to":[0,0,0],"ti":[0,-1.994,0]},{"t":160,"s":[53.503,81.92,0]}]},"a":{"a":0,"k":[-68.81,51.101,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[3.013,-15.497],[0.5,14]],"o":[[-3.5,18],[-0.412,-11.536]],"v":[[-103,49.5],[-34.5,51]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.090196078431,0.321568627451,0.298039215686,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.530532418045,0.647307631549,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":3,"nm":"Null 13","parent":1,"sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":0,"s":[122.687,17.18,0],"to":[0,-5.829,0],"ti":[0,-1.667,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":60,"s":[122.687,-17.793,0],"to":[0,1.667,0],"ti":[0,-4.995,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":69,"s":[122.687,27.18,0],"to":[0,4.995,0],"ti":[0,1.667,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":79,"s":[122.687,12.18,0],"to":[0,-1.667,0],"ti":[0,4.995,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.167,"y":0},"t":89,"s":[122.687,17.18,0],"to":[0,-4.995,0],"ti":[0,-1.667,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":150,"s":[122.687,-17.793,0],"to":[0,1.667,0],"ti":[0,-4.995,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":159,"s":[122.687,27.18,0],"to":[0,4.995,0],"ti":[0,1.667,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":169,"s":[122.687,12.18,0],"to":[0,-1.667,0],"ti":[0,-0.833,0]},{"t":179,"s":[122.687,17.18,0]}]},"a":{"a":0,"k":[50,50,0]},"s":{"a":1,"k":[{"i":{"x":[0.58,0.58,0.58],"y":[1,1,1]},"o":{"x":[0.43,0.43,0.43],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.58,0.58,0.58],"y":[1,1,1]},"o":{"x":[0.43,0.43,0.43],"y":[0,0,0]},"t":60,"s":[106,88,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.43,0.43,0.43],"y":[0,0,0]},"t":69,"s":[100,100,100]},{"i":{"x":[0.58,0.58,0.58],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":89,"s":[100,100,100]},{"i":{"x":[0.58,0.58,0.58],"y":[1,1,1]},"o":{"x":[0.43,0.43,0.43],"y":[0,0,0]},"t":150,"s":[106,88,100]},{"i":{"x":[0.58,0.58,0.58],"y":[1,1,1]},"o":{"x":[0.43,0.43,0.43],"y":[0,0,0]},"t":159,"s":[100,100,100]},{"t":179,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null 12","parent":1,"sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":0,"s":[-25.313,16.18,0],"to":[0,-5.829,0],"ti":[0,-1.667,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":60,"s":[-25.313,-18.793,0],"to":[0,1.667,0],"ti":[0,-4.995,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":69,"s":[-25.313,26.18,0],"to":[0,4.995,0],"ti":[0,1.667,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":79,"s":[-25.313,11.18,0],"to":[0,-1.667,0],"ti":[0,4.995,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.167,"y":0},"t":89,"s":[-25.313,16.18,0],"to":[0,-4.995,0],"ti":[0,-1.667,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":150,"s":[-25.313,-18.793,0],"to":[0,1.667,0],"ti":[0,-4.995,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":159,"s":[-25.313,26.18,0],"to":[0,4.995,0],"ti":[0,1.667,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":169,"s":[-25.313,11.18,0],"to":[0,-1.667,0],"ti":[0,-0.833,0]},{"t":179,"s":[-25.313,16.18,0]}]},"a":{"a":0,"k":[50,50,0]},"s":{"a":1,"k":[{"i":{"x":[0.58,0.58,0.58],"y":[1,1,1]},"o":{"x":[0.43,0.43,0.43],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.58,0.58,0.58],"y":[1,1,1]},"o":{"x":[0.43,0.43,0.43],"y":[0,0,0]},"t":60,"s":[106,88,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.43,0.43,0.43],"y":[0,0,0]},"t":69,"s":[100,100,100]},{"i":{"x":[0.58,0.58,0.58],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":89,"s":[100,100,100]},{"i":{"x":[0.58,0.58,0.58],"y":[1,1,1]},"o":{"x":[0.43,0.43,0.43],"y":[0,0,0]},"t":150,"s":[106,88,100]},{"i":{"x":[0.58,0.58,0.58],"y":[1,1,1]},"o":{"x":[0.43,0.43,0.43],"y":[0,0,0]},"t":159,"s":[100,100,100]},{"t":179,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 16","parent":7,"sr":1,"ks":{"p":{"a":0,"k":[48.063,25.445,0]},"a":{"a":0,"k":[68.25,-3.375,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.44,"y":0},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[51.5,-19.75],[58.25,-8.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[56.898,-16.424],[58.961,-2.926]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[49.248,1.503],[60.539,9.436]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":102,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[49.248,1.503],[60.539,9.436]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":110,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[48.162,-2.338],[58.748,-4.594]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":118,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[51.5,-19.75],[58.25,-8.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":120,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[51.5,-19.75],[58.25,-8.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":129,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[49.248,1.503],[60.539,9.436]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":131,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[49.248,1.503],[60.539,9.436]],"c":false}]},{"t":140,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[51.5,-19.75],[58.25,-8.5]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.44,"y":0},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[37.5,-9.25],[48.25,0]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[44.635,-10.33],[48.43,3.998]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[34.886,9.442],[48.829,12.863]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":102,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[34.886,9.442],[48.829,12.863]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":110,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[36.04,8.039],[48.376,2.801]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":118,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[37.5,-9.25],[48.25,0]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":120,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[37.5,-9.25],[48.25,0]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":129,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[34.886,9.442],[48.829,12.863]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":131,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[34.886,9.442],[48.829,12.863]],"c":false}]},{"t":140,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[37.5,-9.25],[48.25,0]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.44,"y":0},"t":90,"s":[{"i":[[0,0],[0,0],[-24.752,0.961],[0,0]],"o":[[0,0],[0,0],[25.75,-1],[0,0]],"v":[[29.25,6.25],[40.5,13],[73.5,-12.75],[107.25,12.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96,"s":[{"i":[[0,0],[0,0],[-23.749,0.889],[0,0]],"o":[[0,0],[0,0],[24.387,-0.914],[0,0]],"v":[[33.619,4.128],[40.313,15.653],[73.194,-5.924],[107.245,14.103]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0],[-21.526,0.73],[0,0]],"o":[[0,0],[0,0],[21.365,-0.724],[0,0]],"v":[[25.839,23.692],[39.897,21.538],[72.514,9.214],[107.235,17.657]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":102,"s":[{"i":[[0,0],[0,0],[-21.526,0.73],[0,0]],"o":[[0,0],[0,0],[21.365,-0.724],[0,0]],"v":[[25.839,23.692],[39.897,21.538],[72.514,9.214],[107.235,17.657]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":110,"s":[{"i":[[0,0],[0,0],[-24.049,0.911],[0,0]],"o":[[0,0],[0,0],[24.795,-0.94],[0,0]],"v":[[28.065,22.437],[40.369,14.86],[73.285,-7.966],[107.247,13.623]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":118,"s":[{"i":[[0,0],[0,0],[-24.752,0.961],[0,0]],"o":[[0,0],[0,0],[25.75,-1],[0,0]],"v":[[29.25,6.25],[40.5,13],[73.5,-12.75],[107.25,12.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":120,"s":[{"i":[[0,0],[0,0],[-24.752,0.961],[0,0]],"o":[[0,0],[0,0],[25.75,-1],[0,0]],"v":[[29.25,6.25],[40.5,13],[73.5,-12.75],[107.25,12.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":129,"s":[{"i":[[0,0],[0,0],[-21.526,0.73],[0,0]],"o":[[0,0],[0,0],[21.365,-0.724],[0,0]],"v":[[25.839,23.692],[39.897,21.538],[72.514,9.214],[107.235,17.657]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":131,"s":[{"i":[[0,0],[0,0],[-21.526,0.73],[0,0]],"o":[[0,0],[0,0],[21.365,-0.724],[0,0]],"v":[[25.839,23.692],[39.897,21.538],[72.514,9.214],[107.235,17.657]],"c":false}]},{"t":140,"s":[{"i":[[0,0],[0,0],[-24.752,0.961],[0,0]],"o":[[0,0],[0,0],[25.75,-1],[0,0]],"v":[[29.25,6.25],[40.5,13],[73.5,-12.75],[107.25,12.5]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.44,"y":0},"t":90,"s":[{"i":[[-4.319,19.919],[8.178,-16.798],[0,0],[-22.266,-0.237],[0,0]],"o":[[-24.74,-35.941],[5.882,3.803],[0,0],[22.266,0.237],[0,0]],"v":[[123.419,-15.966],[17.969,-6.092],[39.163,13.953],[72.004,-12.951],[107.099,11.372]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[-4.319,19.919],[8.178,-16.798],[0,0],[-22.266,-0.237],[0,0]],"o":[[-24.74,-35.941],[5.882,3.803],[0,0],[22.266,0.237],[0,0]],"v":[[123.419,-15.966],[17.969,-6.092],[38.76,21.45],[71.768,9.233],[109.548,16.409]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":102,"s":[{"i":[[-4.319,19.919],[8.178,-16.798],[0,0],[-22.266,-0.237],[0,0]],"o":[[-24.74,-35.941],[5.882,3.803],[0,0],[22.266,0.237],[0,0]],"v":[[123.419,-15.966],[17.969,-6.092],[38.76,21.45],[71.768,9.233],[109.548,16.409]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":114,"s":[{"i":[[-4.319,19.919],[8.178,-16.798],[0,0],[-22.266,-0.237],[0,0]],"o":[[-24.74,-35.941],[5.882,3.803],[0,0],[22.266,0.237],[0,0]],"v":[[123.419,-15.966],[17.969,-6.092],[39.163,13.953],[72.004,-12.951],[107.099,11.372]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":120,"s":[{"i":[[-4.319,19.919],[8.178,-16.798],[0,0],[-22.266,-0.237],[0,0]],"o":[[-24.74,-35.941],[5.882,3.803],[0,0],[22.266,0.237],[0,0]],"v":[[123.419,-15.966],[17.969,-6.092],[39.163,13.953],[72.004,-12.951],[107.099,11.372]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":129,"s":[{"i":[[-4.319,19.919],[8.178,-16.798],[0,0],[-22.266,-0.237],[0,0]],"o":[[-24.74,-35.941],[5.882,3.803],[0,0],[22.266,0.237],[0,0]],"v":[[123.419,-15.966],[17.969,-6.092],[38.76,21.45],[71.768,9.233],[109.548,16.409]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":131,"s":[{"i":[[-4.319,19.919],[8.178,-16.798],[0,0],[-22.266,-0.237],[0,0]],"o":[[-24.74,-35.941],[5.882,3.803],[0,0],[22.266,0.237],[0,0]],"v":[[123.419,-15.966],[17.969,-6.092],[38.76,21.45],[71.768,9.233],[109.548,16.409]],"c":true}]},{"t":140,"s":[{"i":[[-4.319,19.919],[8.178,-16.798],[0,0],[-22.266,-0.237],[0,0]],"o":[[-24.74,-35.941],[5.882,3.803],[0,0],[22.266,0.237],[0,0]],"v":[[123.419,-15.966],[17.969,-6.092],[39.163,13.953],[72.004,-12.951],[107.099,11.372]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.898039275525,0.400000029919,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 4","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Shape Layer 20","parent":7,"sr":1,"ks":{"p":{"a":0,"k":[54.7,66.07,0]},"a":{"a":0,"k":[-68.113,37.25,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.57,"y":1},"o":{"x":0.4,"y":0},"t":0,"s":[{"i":[[0,0],[-3.25,-4.5],[0,0],[3.5,9]],"o":[[0,0],[17.5,-8],[0,0],[-3.5,-9]],"v":[[-102,17.25],[-96.5,37.25],[-41,37.25],[-35,12.75]],"c":false}]},{"i":{"x":0.57,"y":1},"o":{"x":0.4,"y":0},"t":60,"s":[{"i":[[0,0],[-1.374,-1.898],[0,0],[2.132,8.247]],"o":[[0,0],[33.789,-25.524],[0,0],[-2.417,-9.349]],"v":[[-102,17.25],[-100.433,29.474],[-37.299,28.278],[-35,12.75]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0},"t":69,"s":[{"i":[[0,0],[-3.25,-4.5],[0,0],[3.5,9]],"o":[[0,0],[17.5,-8],[0,0],[-3.5,-9]],"v":[[-102,17.25],[-96.5,37.25],[-41,37.25],[-35,12.75]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.44,"y":0},"t":90,"s":[{"i":[[0,0],[-3.25,-4.5],[0,0],[3.5,9]],"o":[[0,0],[17.5,-8],[0,0],[-3.5,-9]],"v":[[-102,17.25],[-96.5,37.25],[-41,37.25],[-35,12.75]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[-2.955,-4.091],[0,0],[0.855,4.922]],"o":[[0,0],[27.448,-23.315],[0,0],[-1.652,-9.505]],"v":[[-102,17.25],[-101.038,20.236],[-35.295,18.057],[-35,12.75]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":102,"s":[{"i":[[0,0],[-2.955,-4.091],[0,0],[0.855,4.922]],"o":[[0,0],[27.448,-23.315],[0,0],[-1.652,-9.505]],"v":[[-102,17.25],[-101.038,20.236],[-35.295,18.057],[-35,12.75]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.44,"y":0},"t":114,"s":[{"i":[[0,0],[-3.25,-4.5],[0,0],[3.5,9]],"o":[[0,0],[17.5,-8],[0,0],[-3.5,-9]],"v":[[-102,17.25],[-96.5,37.25],[-41,37.25],[-35,12.75]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[0,0],[-3.152,-4.364],[0,0],[3.428,8.961]],"o":[[0,0],[18.352,-8.916],[0,0],[-3.443,-9.018]],"v":[[-102,17.25],[-96.706,36.844],[-40.807,36.781],[-35,12.75]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":129,"s":[{"i":[[0,0],[-2.955,-4.091],[0,0],[0.855,4.922]],"o":[[0,0],[27.448,-23.315],[0,0],[-1.652,-9.505]],"v":[[-102,17.25],[-101.038,20.236],[-35.295,18.057],[-35,12.75]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":131,"s":[{"i":[[0,0],[-2.955,-4.091],[0,0],[0.855,4.922]],"o":[[0,0],[27.448,-23.315],[0,0],[-1.652,-9.505]],"v":[[-102,17.25],[-101.038,20.236],[-35.295,18.057],[-35,12.75]],"c":false}]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0.167},"t":140,"s":[{"i":[[0,0],[-1.638,-2.264],[0,0],[2.324,8.353]],"o":[[0,0],[31.497,-23.058],[0,0],[-2.569,-9.3]],"v":[[-102,17.25],[-99.879,30.569],[-37.82,29.541],[-35,12.75]],"c":false}]},{"i":{"x":0.57,"y":1},"o":{"x":0.4,"y":0},"t":150,"s":[{"i":[[0,0],[-1.374,-1.898],[0,0],[2.132,8.247]],"o":[[0,0],[33.789,-25.524],[0,0],[-2.417,-9.349]],"v":[[-102,17.25],[-100.433,29.474],[-37.299,28.278],[-35,12.75]],"c":false}]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0},"t":159,"s":[{"i":[[0,0],[-3.25,-4.5],[0,0],[3.5,9]],"o":[[0,0],[17.5,-8],[0,0],[-3.5,-9]],"v":[[-102,17.25],[-96.5,37.25],[-41,37.25],[-35,12.75]],"c":false}]},{"t":179,"s":[{"i":[[0,0],[-3.25,-4.5],[0,0],[3.5,9]],"o":[[0,0],[17.5,-8],[0,0],[-3.5,-9]],"v":[[-102,17.25],[-96.5,37.25],[-41,37.25],[-35,12.75]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.980392216701,0.749019607843,0.290196078431,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":4},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.57,"y":1},"o":{"x":0.4,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-39,-8.5]],"v":[[41.75,38.25],[55.5,55],[92.25,55],[105.25,38]],"c":true}]},{"i":{"x":0.57,"y":1},"o":{"x":0.4,"y":0},"t":60,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-36.759,-31.505]],"v":[[40.362,33.166],[55.5,55],[92.25,55],[108.026,32.019]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0},"t":69,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-39,-8.5]],"v":[[41.75,38.25],[55.5,55],[92.25,55],[105.25,38]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.44,"y":0},"t":90,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-39,-8.5]],"v":[[41.75,38.25],[55.5,55],[92.25,55],[105.25,38]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0],[0,0],[0.83,11.642]],"o":[[0,0],[0,0],[0,0],[-43.486,-24.082]],"v":[[40.144,23.081],[43.41,49.664],[110.408,52.561],[109.097,19.619]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":102,"s":[{"i":[[0,0],[0,0],[0,0],[0.83,11.642]],"o":[[0,0],[0,0],[0,0],[-43.486,-24.082]],"v":[[40.144,23.081],[43.41,49.664],[110.408,52.561],[109.097,19.619]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.44,"y":0},"t":114,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-39,-8.5]],"v":[[41.75,38.25],[55.5,55],[92.25,55],[105.25,38]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-38.883,-9.703]],"v":[[41.677,37.984],[55.5,55],[92.25,55],[105.395,37.687]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":129,"s":[{"i":[[0,0],[0,0],[0,0],[0.83,11.642]],"o":[[0,0],[0,0],[0,0],[-43.486,-24.082]],"v":[[40.144,23.081],[43.41,49.664],[110.408,52.561],[109.097,19.619]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":131,"s":[{"i":[[0,0],[0,0],[0,0],[0.83,11.642]],"o":[[0,0],[0,0],[0,0],[-43.486,-24.082]],"v":[[40.144,23.081],[43.41,49.664],[110.408,52.561],[109.097,19.619]],"c":true}]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0.167},"t":140,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-37.074,-28.267]],"v":[[40.557,33.881],[55.5,55],[92.25,55],[107.635,32.86]],"c":true}]},{"i":{"x":0.57,"y":1},"o":{"x":0.4,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-36.759,-31.505]],"v":[[40.362,33.166],[55.5,55],[92.25,55],[108.026,32.019]],"c":true}]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0},"t":159,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-39,-8.5]],"v":[[41.75,38.25],[55.5,55],[92.25,55],[105.25,38]],"c":true}]},{"t":179,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-39,-8.5]],"v":[[41.75,38.25],[55.5,55],[92.25,55],[105.25,38]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.898039275525,0.400000029919,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-143,-0.75]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 2","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Shape Layer 15","parent":7,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":0,"s":[44.462,37.403,0],"to":[0.756,2.786,0],"ti":[-2.728,-2.463,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[49,54.12,0],"to":[2.728,2.463,0],"ti":[-1.978,1.315,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0.167},"t":102,"s":[60.832,52.182,0],"to":[1.978,-1.315,0],"ti":[2.728,2.463,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.45,"y":0},"t":130,"s":[60.867,46.227,0],"to":[-2.728,-2.463,0],"ti":[2.734,1.471,0]},{"t":143,"s":[44.462,37.403,0]}]},"a":{"a":0,"k":[-84.813,28.801,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[12.25,-6.75],[-11.25,9.5]],"o":[[-13.62,7.505],[8.24,-6.958]],"v":[[-77,12],[-64.25,28.25]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 4","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[5.5,-7],[-4.168,5.28]],"o":[[-5.5,7],[3.75,-4.75]],"v":[[-104,23],[-96.25,29.75]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[12.066,7.023],[-4.964,-9.692],[-9.219,4.491]],"o":[[-15.838,-9.219],[3.45,6.736],[6.853,-3.339]],"v":[[-81.816,20.469],[-104.036,38.198],[-82.998,45.29]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.407283079858,0.757814654182,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[2.75,0.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[3.865,-16.924],[-7.092,28.582],[10.332,3.335]],"o":[[-7.25,31.75],[3.717,-14.98],[-12.601,-4.067]],"v":[[-108.75,25],[-61,35],[-76.41,5.722]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.01568627451,0.325490196078,0.898039275525,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Shape Layer 14","parent":7,"sr":1,"ks":{"p":{"a":0,"k":[54.249,47.267,0]},"a":{"a":0,"k":[74.436,18.447,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[14,-17.25],[-10.519,-6.972],[-6.25,5.75],[13.25,15.5]],"o":[[-12.031,14.824],[21.5,14.25],[6.25,-5.75],[-14.49,-16.951]],"v":[[48,-1],[51.75,42.5],[98.25,41.25],[101.25,0.75]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Shape Layer 8","parent":6,"sr":1,"ks":{"p":{"a":0,"k":[48.063,25.445,0]},"a":{"a":0,"k":[68.25,-3.375,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.43,"y":0},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[51.5,-19.75],[58.25,-8.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[53.462,-17.974],[57.052,-3.799]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[44.559,-3.939],[55.523,2.205]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":102,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[43.705,-1.273],[55.523,2.205]],"c":false}]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0.167},"t":109,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[46.329,-10.316],[57.635,-6.085]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.43,"y":0},"t":114,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[51.5,-19.75],[58.25,-8.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":120,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[51.5,-19.75],[58.25,-8.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":129,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[44.559,-3.939],[55.523,2.205]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":131,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[43.705,-1.273],[55.523,2.205]],"c":false}]},{"t":140,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[51.5,-19.75],[58.25,-8.5]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.43,"y":0},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[37.5,-9.25],[48.25,0]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[41.719,-9.391],[47.985,3.749]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[34.196,5.093],[47.647,8.538]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":102,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[34.524,6.665],[47.647,8.538]],"c":false}]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0.167},"t":109,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[34.615,-0.725],[48.114,1.926]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.43,"y":0},"t":114,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[37.5,-9.25],[48.25,0]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":120,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[37.5,-9.25],[48.25,0]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":129,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[34.196,5.093],[47.647,8.538]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":131,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[34.524,6.665],[47.647,8.538]],"c":false}]},{"t":140,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[37.5,-9.25],[48.25,0]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.43,"y":0},"t":90,"s":[{"i":[[0,0],[0,0],[-24.752,0.961],[0,0]],"o":[[0,0],[0,0],[25.75,-1],[0,0]],"v":[[29.25,6.25],[40.5,13],[73.5,-12.75],[107.25,12.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96,"s":[{"i":[[0,0],[0,0],[-24.709,1.584],[0,0]],"o":[[0,0],[0,0],[24.322,-1.514],[0,0]],"v":[[33.45,2.178],[40.659,14.124],[73.229,-6.736],[107.25,12.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0],[-24.656,2.379],[0,0]],"o":[[0,0],[0,0],[22.497,-2.171],[0,0]],"v":[[27.954,15.853],[40.862,15.561],[72.882,0.945],[107.25,12.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":102,"s":[{"i":[[0,0],[0,0],[-24.656,2.379],[0,0]],"o":[[0,0],[0,0],[22.497,-2.171],[0,0]],"v":[[29.226,16.598],[40.862,15.561],[72.882,0.945],[107.25,12.5]],"c":false}]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0.167},"t":109,"s":[{"i":[[0,0],[0,0],[-24.73,1.281],[0,0]],"o":[[0,0],[0,0],[25.016,-1.264],[0,0]],"v":[[28.173,11.853],[40.582,13.578],[73.361,-9.661],[107.25,12.5]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.43,"y":0},"t":114,"s":[{"i":[[0,0],[0,0],[-24.752,0.961],[0,0]],"o":[[0,0],[0,0],[25.75,-1],[0,0]],"v":[[29.25,6.25],[40.5,13],[73.5,-12.75],[107.25,12.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":120,"s":[{"i":[[0,0],[0,0],[-24.752,0.961],[0,0]],"o":[[0,0],[0,0],[25.75,-1],[0,0]],"v":[[29.25,6.25],[40.5,13],[73.5,-12.75],[107.25,12.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":129,"s":[{"i":[[0,0],[0,0],[-24.656,2.379],[0,0]],"o":[[0,0],[0,0],[22.497,-2.171],[0,0]],"v":[[27.954,15.853],[40.862,15.561],[72.882,0.945],[107.25,12.5]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":131,"s":[{"i":[[0,0],[0,0],[-24.656,2.379],[0,0]],"o":[[0,0],[0,0],[22.497,-2.171],[0,0]],"v":[[29.226,16.598],[40.862,15.561],[72.882,0.945],[107.25,12.5]],"c":false}]},{"t":140,"s":[{"i":[[0,0],[0,0],[-24.752,0.961],[0,0]],"o":[[0,0],[0,0],[25.75,-1],[0,0]],"v":[[29.25,6.25],[40.5,13],[73.5,-12.75],[107.25,12.5]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.43,"y":0},"t":90,"s":[{"i":[[-21.275,-0.521],[0,0],[-11.673,9.66],[0,0],[0,0]],"o":[[28.762,0.705],[0,0],[-71.151,-69.387],[0,0],[0,0]],"v":[[73.863,-13.085],[106.719,11.565],[125.145,1.924],[22.084,1.205],[40.73,12.578]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[-21.275,-0.521],[0,0],[-11.673,9.66],[0,0],[0,0]],"o":[[28.762,0.705],[0,0],[-71.151,-69.387],[0,0],[0,0]],"v":[[73.491,0.598],[106.719,11.565],[125.145,1.924],[22.084,1.205],[40.73,12.578]],"c":true}]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0.167},"t":102,"s":[{"i":[[-21.275,-0.521],[0,0],[-11.673,9.66],[0,0],[0,0]],"o":[[28.762,0.705],[0,0],[-71.151,-69.387],[0,0],[0,0]],"v":[[73.491,0.598],[106.719,11.565],[125.145,1.924],[22.084,1.205],[40.73,12.578]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.43,"y":0},"t":114,"s":[{"i":[[-21.275,-0.521],[0,0],[-11.673,9.66],[0,0],[0,0]],"o":[[28.762,0.705],[0,0],[-71.151,-69.387],[0,0],[0,0]],"v":[[73.863,-13.085],[106.719,11.565],[125.145,1.924],[22.084,1.205],[40.73,12.578]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":120,"s":[{"i":[[-21.275,-0.521],[0,0],[-11.673,9.66],[0,0],[0,0]],"o":[[28.762,0.705],[0,0],[-71.151,-69.387],[0,0],[0,0]],"v":[[73.863,-13.085],[106.719,11.565],[125.145,1.924],[22.084,1.205],[40.73,12.578]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":129,"s":[{"i":[[-21.275,-0.521],[0,0],[-11.673,9.66],[0,0],[0,0]],"o":[[28.762,0.705],[0,0],[-71.151,-69.387],[0,0],[0,0]],"v":[[73.491,0.598],[106.719,11.565],[125.145,1.924],[22.084,1.205],[40.73,12.578]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":131,"s":[{"i":[[-21.275,-0.521],[0,0],[-11.673,9.66],[0,0],[0,0]],"o":[[28.762,0.705],[0,0],[-71.151,-69.387],[0,0],[0,0]],"v":[[73.491,0.598],[106.719,11.565],[125.145,1.924],[22.084,1.205],[40.73,12.578]],"c":true}]},{"t":140,"s":[{"i":[[-21.275,-0.521],[0,0],[-11.673,9.66],[0,0],[0,0]],"o":[[28.762,0.705],[0,0],[-71.151,-69.387],[0,0],[0,0]],"v":[[73.863,-13.085],[106.719,11.565],[125.145,1.924],[22.084,1.205],[40.73,12.578]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.898039275525,0.400000029919,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 4","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Shape Layer 21","parent":6,"sr":1,"ks":{"p":{"a":0,"k":[54.2,66.32,0]},"a":{"a":0,"k":[-68.113,37.25,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.57,"y":1},"o":{"x":0.4,"y":0},"t":0,"s":[{"i":[[0,0],[-3.25,-4.5],[0,0],[3.5,9]],"o":[[0,0],[17.5,-8],[0,0],[-3.5,-9]],"v":[[-102,17.25],[-96.5,37.25],[-41,37.25],[-35,12.75]],"c":false}]},{"i":{"x":0.57,"y":1},"o":{"x":0.4,"y":0},"t":60,"s":[{"i":[[0,0],[-1.694,-4.779],[0,0],[0.886,4.768]],"o":[[0,0],[22.133,-26.611],[0,0],[-1.765,-9.494]],"v":[[-102,17.25],[-98.582,27.979],[-35.217,24.988],[-35,12.75]],"c":false}]},{"i":{"x":0.57,"y":1},"o":{"x":0.4,"y":0},"t":69,"s":[{"i":[[0,0],[-3.25,-4.5],[0,0],[3.5,9]],"o":[[0,0],[17.5,-8],[0,0],[-3.5,-9]],"v":[[-102,17.25],[-96.5,37.25],[-41,37.25],[-35,12.75]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.43,"y":0},"t":90,"s":[{"i":[[0,0],[-3.25,-4.5],[0,0],[3.5,9]],"o":[[0,0],[17.5,-8],[0,0],[-3.5,-9]],"v":[[-102,17.25],[-96.5,37.25],[-41,37.25],[-35,12.75]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[-3.006,-4.544],[0,0],[0.983,2.663]],"o":[[0,0],[30.456,-31.683],[0,0],[-3.227,-9.078]],"v":[[-105.387,13.291],[-101.614,17.208],[-35.342,14.723],[-33.111,10.852]],"c":false}]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0.167},"t":102,"s":[{"i":[[0,0],[-3.006,-4.544],[0,0],[0.983,2.663]],"o":[[0,0],[30.456,-31.683],[0,0],[-3.227,-9.078]],"v":[[-105.387,13.291],[-101.614,17.208],[-35.342,14.723],[-33.111,10.852]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.43,"y":0},"t":114,"s":[{"i":[[0,0],[-3.25,-4.5],[0,0],[3.5,9]],"o":[[0,0],[17.5,-8],[0,0],[-3.5,-9]],"v":[[-102,17.25],[-96.5,37.25],[-41,37.25],[-35,12.75]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[0,0],[-3.165,-4.515],[0,0],[3.358,8.77]],"o":[[0,0],[17.752,-9.011],[0,0],[-3.406,-9.027]],"v":[[-102,17.25],[-96.613,36.746],[-40.686,36.584],[-35,12.75]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":129,"s":[{"i":[[0,0],[-3.006,-4.544],[0,0],[0.983,2.663]],"o":[[0,0],[30.456,-31.683],[0,0],[-3.227,-9.078]],"v":[[-105.387,13.291],[-101.614,17.208],[-35.342,14.723],[-33.111,10.852]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":131,"s":[{"i":[[0,0],[-3.006,-4.544],[0,0],[0.983,2.663]],"o":[[0,0],[30.456,-31.683],[0,0],[-3.227,-9.078]],"v":[[-105.387,13.291],[-101.614,17.208],[-35.342,14.723],[-33.111,10.852]],"c":false}]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0.167},"t":140,"s":[{"i":[[0,0],[-1.913,-4.74],[0,0],[1.253,5.362]],"o":[[0,0],[21.483,-23.997],[0,0],[-2.008,-9.425]],"v":[[-102,17.25],[-98.29,29.281],[-36.029,26.71],[-35,12.75]],"c":false}]},{"i":{"x":0.57,"y":1},"o":{"x":0.4,"y":0},"t":150,"s":[{"i":[[0,0],[-1.694,-4.779],[0,0],[0.886,4.768]],"o":[[0,0],[22.133,-26.611],[0,0],[-1.765,-9.494]],"v":[[-102,17.25],[-98.582,27.979],[-35.217,24.988],[-35,12.75]],"c":false}]},{"i":{"x":0.57,"y":1},"o":{"x":0.4,"y":0},"t":159,"s":[{"i":[[0,0],[-3.25,-4.5],[0,0],[3.5,9]],"o":[[0,0],[17.5,-8],[0,0],[-3.5,-9]],"v":[[-102,17.25],[-96.5,37.25],[-41,37.25],[-35,12.75]],"c":false}]},{"t":179,"s":[{"i":[[0,0],[-3.25,-4.5],[0,0],[3.5,9]],"o":[[0,0],[17.5,-8],[0,0],[-3.5,-9]],"v":[[-102,17.25],[-96.5,37.25],[-41,37.25],[-35,12.75]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.980392216701,0.749019607843,0.290196078431,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":4},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.57,"y":1},"o":{"x":0.4,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-34.25,-13]],"v":[[-101.75,39],[-89.75,55.25],[-53,55.25],[-37,39.25]],"c":true}]},{"i":{"x":0.57,"y":1},"o":{"x":0.4,"y":0},"t":60,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-41.762,-25.321]],"v":[[-99.437,27.336],[-89.75,55.25],[-53,55.25],[-33.761,26.988]],"c":true}]},{"i":{"x":0.57,"y":1},"o":{"x":0.4,"y":0},"t":69,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-34.25,-13]],"v":[[-101.75,39],[-89.75,55.25],[-53,55.25],[-37,39.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.43,"y":0},"t":90,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-34.25,-13]],"v":[[-101.75,39],[-89.75,55.25],[-53,55.25],[-37,39.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0],[0,0],[3.699,10.773]],"o":[[0,0],[0,0],[0,0],[-38.301,-33.831]],"v":[[-104.203,18.485],[-100.75,52.181],[-36.355,52.112],[-33.723,16.562]],"c":true}]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0.167},"t":102,"s":[{"i":[[0,0],[0,0],[0,0],[3.699,10.773]],"o":[[0,0],[0,0],[0,0],[-38.301,-33.831]],"v":[[-104.203,18.485],[-100.75,52.181],[-36.355,52.112],[-33.723,16.562]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.43,"y":0},"t":114,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-34.25,-13]],"v":[[-101.75,39],[-89.75,55.25],[-53,55.25],[-37,39.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-34.658,-13.67]],"v":[[-101.624,38.366],[-89.75,55.25],[-53,55.25],[-36.824,38.584]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":129,"s":[{"i":[[0,0],[0,0],[0,0],[3.699,10.773]],"o":[[0,0],[0,0],[0,0],[-38.301,-33.831]],"v":[[-104.203,18.485],[-100.75,52.181],[-36.355,52.112],[-33.723,16.562]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":131,"s":[{"i":[[0,0],[0,0],[0,0],[3.699,10.773]],"o":[[0,0],[0,0],[0,0],[-38.301,-33.831]],"v":[[-104.203,18.485],[-100.75,52.181],[-36.355,52.112],[-33.723,16.562]],"c":true}]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0.167},"t":140,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-40.707,-23.591]],"v":[[-99.761,28.974],[-89.75,55.25],[-53,55.25],[-34.216,28.71]],"c":true}]},{"i":{"x":0.57,"y":1},"o":{"x":0.4,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-41.762,-25.321]],"v":[[-99.437,27.336],[-89.75,55.25],[-53,55.25],[-33.761,26.988]],"c":true}]},{"i":{"x":0.57,"y":1},"o":{"x":0.4,"y":0},"t":159,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-34.25,-13]],"v":[[-101.75,39],[-89.75,55.25],[-53,55.25],[-37,39.25]],"c":true}]},{"t":179,"s":[{"i":[[0,0],[0,0],[0,0],[-6,7.25]],"o":[[0,0],[0,0],[0,0],[-34.25,-13]],"v":[[-101.75,39],[-89.75,55.25],[-53,55.25],[-37,39.25]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.898039275525,0.400000029919,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 2","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Shape Layer 13","parent":6,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":0,"s":[44.462,37.403,0],"to":[0.756,2.786,0],"ti":[-2.728,-2.463,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[49,54.12,0],"to":[2.728,2.463,0],"ti":[-1.978,1.315,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0.167},"t":102,"s":[60.832,52.182,0],"to":[1.978,-1.315,0],"ti":[2.728,2.463,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.45,"y":0},"t":130,"s":[60.867,46.227,0],"to":[-2.728,-2.463,0],"ti":[2.734,1.471,0]},{"t":143,"s":[44.462,37.403,0]}]},"a":{"a":0,"k":[-84.813,28.801,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[12.25,-6.75],[-11.25,9.5]],"o":[[-13.62,7.505],[8.24,-6.958]],"v":[[-77,12],[-64.25,28.25]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 4","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[5.5,-7],[-4.168,5.28]],"o":[[-5.5,7],[3.75,-4.75]],"v":[[-104,23],[-96.25,29.75]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[12.066,7.023],[-4.964,-9.692],[-9.219,4.491]],"o":[[-15.838,-9.219],[3.45,6.736],[6.853,-3.339]],"v":[[-81.816,20.469],[-104.036,38.198],[-82.998,45.29]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.407283079858,0.757814654182,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[2.75,0.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[3.865,-16.924],[-7.092,28.582],[10.332,3.335]],"o":[[-7.25,31.75],[3.717,-14.98],[-12.601,-4.067]],"v":[[-108.75,25],[-61,35],[-76.41,5.722]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.01568627451,0.325490196078,0.898039275525,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Shape Layer 10","parent":6,"sr":1,"ks":{"p":{"a":0,"k":[54.249,47.267,0]},"a":{"a":0,"k":[74.436,18.447,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[14,-17.25],[-10.519,-6.972],[-6.25,5.75],[13.25,15.5]],"o":[[-12.031,14.824],[21.5,14.25],[6.25,-5.75],[-14.49,-16.951]],"v":[[48,-1],[51.75,42.5],[98.25,41.25],[101.25,0.75]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"Shape Layer 4","parent":1,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":0,"s":[50.25,29.254,0],"to":[0,-7.083,0],"ti":[0,-1.667,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":60,"s":[50.25,-13.246,0],"to":[0,1.667,0],"ti":[0,-6.25,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":69,"s":[50.25,39.254,0],"to":[0,6.25,0],"ti":[0,1.667,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":79,"s":[50.25,24.254,0],"to":[0,-1.667,0],"ti":[0,6.25,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.167,"y":0},"t":89,"s":[50.25,29.254,0],"to":[0,-6.25,0],"ti":[0,-1.667,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":150,"s":[50.25,-13.246,0],"to":[0,1.667,0],"ti":[0,-6.25,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":159,"s":[50.25,39.254,0],"to":[0,6.25,0],"ti":[0,1.667,0]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":169,"s":[50.25,24.254,0],"to":[0,-1.667,0],"ti":[0,-0.833,0]},{"t":179,"s":[50.25,29.254,0]}]},"a":{"a":0,"k":[-2.25,33.254,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":0,"s":[{"i":[[0,0],[-18,0.5],[0,0]],"o":[[0,0],[18,-0.5],[0,0]],"v":[[-22.5,23.5],[-1.5,44],[18,22.5]],"c":false}]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":60,"s":[{"i":[[0,0],[-30.488,-0.871],[0,0]],"o":[[0,0],[35,1],[0,0]],"v":[[-20,22.5],[-1.5,40],[18,21.5]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.43,"y":0},"t":69,"s":[{"i":[[0,0],[-18,0.5],[0,0]],"o":[[0,0],[18,-0.5],[0,0]],"v":[[-22.5,23.5],[-1.5,44],[18,22.5]],"c":false}]},{"i":{"x":0.58,"y":1},"o":{"x":0.167,"y":0},"t":89,"s":[{"i":[[0,0],[-18,0.5],[0,0]],"o":[[0,0],[18,-0.5],[0,0]],"v":[[-22.5,23.5],[-1.5,44],[18,22.5]],"c":false}]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":150,"s":[{"i":[[0,0],[-30.488,-0.871],[0,0]],"o":[[0,0],[35,1],[0,0]],"v":[[-20,22.5],[-1.5,40],[18,21.5]],"c":false}]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":159,"s":[{"i":[[0,0],[-18,0.5],[0,0]],"o":[[0,0],[18,-0.5],[0,0]],"v":[[-22.5,23.5],[-1.5,44],[18,22.5]],"c":false}]},{"t":179,"s":[{"i":[[0,0],[-18,0.5],[0,0]],"o":[[0,0],[18,-0.5],[0,0]],"v":[[-22.5,23.5],[-1.5,44],[18,22.5]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.662745098039,0.003921568627,0.003921568627,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"Shape Layer 22","parent":16,"sr":1,"ks":{"p":{"a":0,"k":[-4.057,5.683,0]},"a":{"a":0,"k":[0.366,0.002,0]},"s":{"a":0,"k":[104.167,104.167,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[19.5,-0.5],[-18.496,0.627]],"o":[[-25.996,0.667],[29.5,-1]],"v":[[0,-12],[-2,12]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.090196078431,0.321568627451,0.298039215686,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.996078491211,0.956862804936,0.815686334348,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0.5,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 6","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":"STAR","parent":19,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.57],"y":[1]},"o":{"x":[0.41],"y":[0]},"t":0,"s":[-9]},{"i":{"x":[0.57],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":60,"s":[0]},{"i":{"x":[0.57],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":70,"s":[10]},{"i":{"x":[0.57],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":80,"s":[7]},{"i":{"x":[0.57],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":90,"s":[8]},{"i":{"x":[0.57],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":150,"s":[0]},{"t":160,"s":[-9]}]},"p":{"a":1,"k":[{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":0,"s":[2.84,165.57,0],"to":[0.909,-4.283,0],"ti":[-1.653,-1.65,0]},{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":60,"s":[8.291,139.874,0],"to":[1.653,1.65,0],"ti":[-0.744,-3.432,0]},{"i":{"x":0.6,"y":1},"o":{"x":0.41,"y":0},"t":69,"s":[12.755,175.473,0],"to":[0.744,3.432,0],"ti":[-0.001,1.677,0]},{"i":{"x":0.6,"y":1},"o":{"x":0.41,"y":0},"t":79,"s":[12.756,160.467,0],"to":[0.001,-1.677,0],"ti":[0.827,3.432,0]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0},"t":89,"s":[12.763,165.408,0],"to":[-0.827,-3.432,0],"ti":[0.829,-1.578,0]},{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":150,"s":[7.791,139.874,0],"to":[-0.829,1.578,0],"ti":[1.65,-3.565,0]},{"i":{"x":0.6,"y":1},"o":{"x":0.41,"y":0},"t":159,"s":[7.791,174.874,0],"to":[-1.65,3.565,0],"ti":[0.825,1.551,0]},{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":169,"s":[-2.111,161.266,0],"to":[-0.825,-1.551,0],"ti":[-0.825,-0.717,0]},{"t":179,"s":[2.84,165.57,0]}]},"a":{"a":0,"k":[7.791,164.874,0]},"s":{"a":1,"k":[{"i":{"x":[0.59,0.59,0.59],"y":[1,1,1]},"o":{"x":[0.41,0.41,0.41],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.59,0.59,0.59],"y":[1,1,1]},"o":{"x":[0.41,0.41,0.41],"y":[0,0,0]},"t":60,"s":[101.95,94.993,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":69,"s":[100,100,100]},{"i":{"x":[0.59,0.59,0.59],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":89,"s":[100,100,100]},{"i":{"x":[0.59,0.59,0.59],"y":[1,1,1]},"o":{"x":[0.41,0.41,0.41],"y":[0,0,0]},"t":150,"s":[101.95,94.993,100]},{"i":{"x":[0.59,0.59,0.59],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":159,"s":[100,100,100]},{"t":179,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":0,"s":[{"i":[[0,0],[-28,-24]],"o":[[0,0],[18.035,15.459]],"v":[[106.5,-45],[196,-12.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":5,"s":[{"i":[[0,0],[-37,-5]],"o":[[0,0],[23.54,3.181]],"v":[[108.5,-52.5],[204,-62.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[0,0],[-29.671,-31.602]],"o":[[0,0],[19.876,11.353]],"v":[[108.5,-45.844],[204.171,-28.898]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":10,"s":[{"i":[[0,0],[-28,-24]],"o":[[0,0],[18.035,15.459]],"v":[[108.5,-42.5],[200.5,-4.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":15,"s":[{"i":[[0,0],[-37,-5]],"o":[[0,0],[23.54,3.181]],"v":[[108.5,-55.5],[202,-66]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[0,0],[-26.007,-40.607]],"o":[[0,0],[19.876,11.353]],"v":[[107.169,-48.512],[207.507,-29.393]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":20,"s":[{"i":[[0,0],[-28,-24]],"o":[[0,0],[18.035,15.459]],"v":[[105.007,-53.601],[196,-12.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":23,"s":[{"i":[[0,0],[-33.99,-11.354]],"o":[[0,0],[21.699,7.287]],"v":[[106.338,-51.183],[201.324,-45.778]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[0,0],[-36.02,-7.069]],"o":[[0,0],[22.941,4.518]],"v":[[108.789,-53.524],[203.129,-57.055]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":25,"s":[{"i":[[0,0],[-37,-5]],"o":[[0,0],[23.54,3.181]],"v":[[108.5,-63.702],[204,-62.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[0,0],[-29.671,-31.602]],"o":[[0,0],[19.876,11.353]],"v":[[107.013,-61.185],[204.171,-28.898]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":30,"s":[{"i":[[0,0],[-33.057,-35.032]],"o":[[0,0],[16.303,17.277]],"v":[[106.519,-57.372],[200.5,-4.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":33,"s":[{"i":[[0,0],[-35.681,-15.044]],"o":[[0,0],[21.12,7.895]],"v":[[104.377,-69.003],[201.498,-45.432]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":35,"s":[{"i":[[0,0],[-37,-5]],"o":[[0,0],[23.54,3.181]],"v":[[105.537,-68.93],[202,-66]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":38,"s":[{"i":[[0,0],[-26.007,-40.607]],"o":[[0,0],[19.876,11.353]],"v":[[106.183,-73.405],[207.507,-29.393]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":40,"s":[{"i":[[0,0],[-28,-24]],"o":[[0,0],[18.035,15.459]],"v":[[105.022,-63.197],[196,-12.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0,0],[-31.448,-18.873]],"o":[[0,0],[21.699,7.287]],"v":[[103.401,-71.728],[201.324,-45.778]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":45,"s":[{"i":[[0,0],[-38.175,-10.491]],"o":[[0,0],[22.905,6.295]],"v":[[105.058,-68.181],[204,-62.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":48,"s":[{"i":[[0,0],[-30.14,-40.279]],"o":[[0,0],[19.876,11.353]],"v":[[102.605,-67.854],[204.171,-28.898]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":50,"s":[{"i":[[0,0],[-28.026,-31.252]],"o":[[0,0],[15.859,17.685]],"v":[[103.59,-60.866],[200.5,-4.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":53,"s":[{"i":[[0,0],[-38.442,-21.901]],"o":[[0,0],[20.971,8.032]],"v":[[102.442,-68.857],[201.498,-45.432]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":55,"s":[{"i":[[0,0],[-37,-5]],"o":[[0,0],[23.54,3.181]],"v":[[105.066,-66.019],[202,-66]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":58,"s":[{"i":[[0,0],[-26.007,-40.607]],"o":[[0,0],[19.876,11.353]],"v":[[102.755,-66.932],[207.507,-29.393]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":60,"s":[{"i":[[0,0],[-28,-24]],"o":[[0,0],[18.035,15.459]],"v":[[106.5,-45],[196,-12.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":64,"s":[{"i":[[0,0],[-28,-24]],"o":[[0,0],[18.035,15.459]],"v":[[106.5,-45],[202.437,-25.321]],"c":false}]},{"i":{"x":0.61,"y":1},"o":{"x":0.167,"y":0.167},"t":67,"s":[{"i":[[0,0],[-28,-24]],"o":[[0,0],[18.035,15.459]],"v":[[106.5,-45],[202.437,-25.321]],"c":false}]},{"i":{"x":0.61,"y":1},"o":{"x":0.41,"y":0},"t":73,"s":[{"i":[[0,0],[-28,-24]],"o":[[0,0],[18.035,15.459]],"v":[[105.5,-41],[199.5,6.5]],"c":false}]},{"i":{"x":0.61,"y":1},"o":{"x":0.41,"y":0},"t":83,"s":[{"i":[[0,0],[-28,-24]],"o":[[0,0],[18.035,15.459]],"v":[[107,-46.5],[197.5,-22]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0},"t":89,"s":[{"i":[[0,0],[-28,-24]],"o":[[0,0],[18.035,15.459]],"v":[[106.5,-45],[196,-12.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":95,"s":[{"i":[[0,0],[-37,-5]],"o":[[0,0],[23.54,3.181]],"v":[[108.5,-52.5],[204,-62.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":98,"s":[{"i":[[0,0],[-29.671,-31.602]],"o":[[0,0],[19.876,11.353]],"v":[[108.5,-45.844],[204.171,-28.898]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":100,"s":[{"i":[[0,0],[-28,-24]],"o":[[0,0],[18.035,15.459]],"v":[[108.5,-42.5],[200.5,-4.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":105,"s":[{"i":[[0,0],[-37,-5]],"o":[[0,0],[23.54,3.181]],"v":[[108.5,-55.5],[202,-66]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":108,"s":[{"i":[[0,0],[-26.007,-40.607]],"o":[[0,0],[19.876,11.353]],"v":[[107.169,-48.512],[207.507,-29.393]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":110,"s":[{"i":[[0,0],[-28,-24]],"o":[[0,0],[18.035,15.459]],"v":[[105.007,-53.601],[196,-12.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":113,"s":[{"i":[[0,0],[-33.99,-11.354]],"o":[[0,0],[21.699,7.287]],"v":[[108.323,-52.128],[201.324,-45.778]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":114,"s":[{"i":[[0,0],[-36.02,-7.069]],"o":[[0,0],[22.941,4.518]],"v":[[109.928,-53.266],[203.129,-57.055]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":115,"s":[{"i":[[0,0],[-37,-5]],"o":[[0,0],[23.54,3.181]],"v":[[108.5,-63.702],[204,-62.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,0],[-29.671,-31.602]],"o":[[0,0],[19.876,11.353]],"v":[[107.013,-61.185],[204.171,-28.898]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":120,"s":[{"i":[[0,0],[-33.057,-35.032]],"o":[[0,0],[16.303,17.277]],"v":[[106.519,-57.372],[200.5,-4.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":123,"s":[{"i":[[0,0],[-35.681,-15.044]],"o":[[0,0],[21.12,7.895]],"v":[[104.377,-69.003],[201.498,-45.432]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":125,"s":[{"i":[[0,0],[-37,-5]],"o":[[0,0],[23.54,3.181]],"v":[[105.537,-68.93],[202,-66]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":128,"s":[{"i":[[0,0],[-26.007,-40.607]],"o":[[0,0],[19.876,11.353]],"v":[[106.183,-73.405],[207.507,-29.393]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":130,"s":[{"i":[[0,0],[-28,-24]],"o":[[0,0],[18.035,15.459]],"v":[[105.022,-63.197],[196,-12.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":133,"s":[{"i":[[0,0],[-31.448,-18.873]],"o":[[0,0],[21.699,7.287]],"v":[[103.401,-71.728],[201.324,-45.778]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":135,"s":[{"i":[[0,0],[-38.175,-10.491]],"o":[[0,0],[22.905,6.295]],"v":[[105.058,-68.181],[204,-62.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":138,"s":[{"i":[[0,0],[-30.14,-40.279]],"o":[[0,0],[19.876,11.353]],"v":[[102.605,-67.854],[204.171,-28.898]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":140,"s":[{"i":[[0,0],[-28.026,-31.252]],"o":[[0,0],[15.859,17.685]],"v":[[103.59,-60.866],[200.5,-4.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":143,"s":[{"i":[[0,0],[-38.442,-21.901]],"o":[[0,0],[20.971,8.032]],"v":[[102.442,-68.857],[201.498,-45.432]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":145,"s":[{"i":[[0,0],[-37,-5]],"o":[[0,0],[23.54,3.181]],"v":[[105.066,-66.019],[202,-66]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":148,"s":[{"i":[[0,0],[-26.007,-40.607]],"o":[[0,0],[19.876,11.353]],"v":[[102.755,-66.932],[207.507,-29.393]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":150,"s":[{"i":[[0,0],[-28,-24]],"o":[[0,0],[18.035,15.459]],"v":[[106.5,-45],[196,-12.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":154,"s":[{"i":[[0,0],[-28,-24]],"o":[[0,0],[18.035,15.459]],"v":[[106.5,-45],[202.437,-25.321]],"c":false}]},{"i":{"x":0.61,"y":1},"o":{"x":0.167,"y":0.167},"t":157,"s":[{"i":[[0,0],[-28,-24]],"o":[[0,0],[18.035,15.459]],"v":[[106.5,-45],[202.437,-25.321]],"c":false}]},{"i":{"x":0.61,"y":1},"o":{"x":0.41,"y":0},"t":163,"s":[{"i":[[0,0],[-28,-24]],"o":[[0,0],[18.035,15.459]],"v":[[105.5,-41],[199.5,6.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":173,"s":[{"i":[[0,0],[-28,-24]],"o":[[0,0],[18.035,15.459]],"v":[[107,-46.5],[197.5,-22]],"c":false}]},{"t":179,"s":[{"i":[[0,0],[-28,-24]],"o":[[0,0],[18.035,15.459]],"v":[[106.5,-45],[196,-12.5]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 5","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":0,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[20.436,-170.409],[67.972,-94.221]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":5,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[26.436,-181.909],[69.472,-100.221]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":10,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[26.936,-172.409],[71.472,-94.721]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":15,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[14.436,-176.409],[64.972,-94.721]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":20,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[20.436,-170.409],[67.972,-94.221]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":25,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[26.436,-181.909],[69.472,-100.221]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":30,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[24.46,-163.69],[71.472,-94.721]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":33,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[16.8,-159.279],[67.146,-94.721]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":35,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[10.485,-161.43],[64.972,-94.721]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":38,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[14.149,-160.145],[66.969,-94.388]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":40,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[16.002,-157.931],[67.972,-94.221]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[20.486,-159.283],[68.971,-98.215]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":45,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[21.027,-159.955],[69.472,-100.221]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":48,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[18.574,-153.571],[70.803,-96.561]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":50,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[24.972,-150.894],[71.472,-94.721]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":53,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[16.978,-152.634],[67.146,-94.721]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":55,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[15.908,-156.422],[64.972,-94.721]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":58,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[16.96,-158.363],[66.969,-94.388]],"c":false}]},{"i":{"x":0.61,"y":1},"o":{"x":0.41,"y":0},"t":60,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[20.436,-170.409],[67.972,-94.221]],"c":false}]},{"i":{"x":0.61,"y":1},"o":{"x":0.41,"y":0},"t":73,"s":[{"i":[[0,0],[-10.972,-43.779]],"o":[[0,0],[7.117,28.396]],"v":[[22.436,-166.409],[71.472,-87.221]],"c":false}]},{"i":{"x":0.61,"y":1},"o":{"x":0.41,"y":0},"t":83,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[20.436,-174.909],[68.972,-94.721]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0},"t":89,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[20.436,-170.409],[67.972,-94.221]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":95,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[26.436,-181.909],[69.472,-100.221]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":100,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[26.936,-172.409],[71.472,-94.721]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":105,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[14.436,-176.409],[64.972,-94.721]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":110,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[20.436,-170.409],[67.972,-94.221]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":115,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[26.436,-181.909],[69.472,-100.221]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":120,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[24.46,-163.69],[71.472,-94.721]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":123,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[16.8,-159.279],[67.146,-94.721]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":125,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[10.485,-161.43],[64.972,-94.721]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":128,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[14.149,-160.145],[66.969,-94.388]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":130,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[16.002,-157.931],[67.972,-94.221]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":133,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[20.486,-159.283],[68.971,-98.215]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":135,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[21.027,-159.955],[69.472,-100.221]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":138,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[18.574,-153.571],[70.803,-96.561]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":140,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[24.972,-150.894],[71.472,-94.721]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":143,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[16.978,-152.634],[67.146,-94.721]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":145,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[15.908,-156.422],[64.972,-94.721]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":148,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[16.96,-158.363],[66.969,-94.388]],"c":false}]},{"i":{"x":0.61,"y":1},"o":{"x":0.41,"y":0},"t":150,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[20.436,-170.409],[67.972,-94.221]],"c":false}]},{"i":{"x":0.61,"y":1},"o":{"x":0.41,"y":0},"t":163,"s":[{"i":[[0,0],[-10.972,-43.779]],"o":[[0,0],[7.117,28.396]],"v":[[22.436,-166.409],[71.472,-87.221]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":173,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[20.436,-174.909],[68.972,-94.721]],"c":false}]},{"t":179,"s":[{"i":[[0,0],[-14.604,-40.456]],"o":[[0,0],[9.94,27.535]],"v":[[20.436,-170.409],[67.972,-94.221]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 4","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":0,"s":[{"i":[[20,18],[0,0],[-19.34,-7.646],[0,0],[31.85,7.078],[-10,42.5]],"o":[[15.5,33.5],[0,0],[21.5,8.5],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-217,-1.5],[-133,66.5],[-138,187.5],[-68.5,172.5],[-115.5,164],[-113.5,57]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":3,"s":[{"i":[[17.338,25.654],[0,0],[-18.781,-9.878],[0,0],[31.85,7.078],[-10,42.5]],"o":[[-6.652,46.602],[0,0],[21.915,11.598],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-213.348,-22.602],[-131.669,62.174],[-142.326,182.508],[-66.5,173],[-118.162,163.334],[-109.84,50.344]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[16.436,28.248],[0,0],[-18.591,-10.635],[0,0],[31.85,7.078],[-10,42.5]],"o":[[-9.456,51.485],[0,0],[22.056,12.648],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-207.044,-47.485],[-131.218,60.708],[-143.792,180.817],[-68.5,173],[-119.064,163.109],[-108.599,48.089]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":5,"s":[{"i":[[16,29.5],[0,0],[-18.5,-11],[0,0],[31.85,7.078],[-10,42.5]],"o":[[2,37.5],[0,0],[22.124,13.155],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-204,-59.5],[-131,60],[-144.5,180],[-68.5,173],[-119.5,163],[-108,47]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[18.662,21.846],[0,0],[-19.059,-8.768],[0,0],[31.85,7.078],[-10,42.5]],"o":[[10.985,34.838],[0,0],[21.709,10.057],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-215.314,-15.573],[-133.995,66.656],[-140.174,184.992],[-68.008,172.171],[-116.838,163.666],[-111.993,54.654]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":10,"s":[{"i":[[20,18],[0,0],[-19.34,-7.646],[0,0],[31.85,7.078],[-10,42.5]],"o":[[15.5,33.5],[0,0],[21.5,8.5],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-221,6.5],[-135.5,70],[-138,187.5],[-66.5,172],[-115.5,164],[-114,58.5]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":13,"s":[{"i":[[11.181,38.599],[0,0],[-18.781,-9.878],[0,0],[31.85,7.078],[-10,42.5]],"o":[[-9.819,62.099],[0,0],[21.915,11.598],[0,0],[-18,-4],[-34.174,-12.829]],"v":[[-215.681,-27.599],[-131.839,62.346],[-140.662,184.505],[-67.5,173],[-116.831,165.664],[-112.003,50.513]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":15,"s":[{"i":[[21,27.5],[0,0],[-18.5,-11],[0,0],[31.85,7.078],[-10,42.5]],"o":[[3,32],[0,0],[22.124,13.155],[0,0],[-18,-4],[-32,-14]],"v":[[-208.5,-54.5],[-130,58.5],[-142,183],[-67.5,172.5],[-117.5,166.5],[-111,46.5]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[20.334,21.177],[0,0],[-19.059,-8.768],[0,0],[31.85,7.078],[-10,42.5]],"o":[[7.657,37.225],[0,0],[21.709,10.057],[0,0],[-18,-4],[-36.326,-11.671]],"v":[[-214.157,-19.225],[-131.997,63.824],[-139.338,185.995],[-67.5,172.5],[-116.169,164.836],[-112.664,53.488]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":20,"s":[{"i":[[20,18],[0,0],[-19.34,-7.646],[0,0],[31.85,7.078],[-10,42.5]],"o":[[15.5,33.5],[0,0],[21.5,8.5],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-217,-1.5],[-133,66.5],[-138,187.5],[-68.5,172.5],[-115.5,164],[-113.5,57]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":23,"s":[{"i":[[17.338,25.654],[0,0],[-18.781,-9.878],[0,0],[31.85,7.078],[-10,42.5]],"o":[[-6.652,46.602],[0,0],[21.915,11.598],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-213.348,-22.602],[-131.669,62.174],[-142.326,182.508],[-66.5,173],[-118.162,163.334],[-109.84,50.344]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[16.436,28.248],[0,0],[-18.591,-10.635],[0,0],[31.85,7.078],[-10,42.5]],"o":[[-9.456,51.485],[0,0],[22.056,12.648],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-207.044,-47.485],[-131.218,60.708],[-143.792,180.817],[-68.5,173],[-119.064,163.109],[-108.599,48.089]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":25,"s":[{"i":[[16,29.5],[0,0],[-18.5,-11],[0,0],[31.85,7.078],[-10,42.5]],"o":[[2,37.5],[0,0],[22.124,13.155],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-204,-59.5],[-131,60],[-144.5,180],[-68.5,173],[-119.5,163],[-108,47]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[18.662,21.846],[0,0],[-19.059,-8.768],[0,0],[31.85,7.078],[-10,42.5]],"o":[[10.985,34.838],[0,0],[21.709,10.057],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-215.314,-15.573],[-133.995,66.656],[-140.174,184.992],[-68.008,172.171],[-116.838,163.666],[-111.993,54.654]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":30,"s":[{"i":[[20,18],[0,0],[-19.34,-7.646],[0,0],[31.85,7.078],[-10,42.5]],"o":[[15.5,33.5],[0,0],[21.5,8.5],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-221,6.5],[-135.5,70],[-138,187.5],[-66.5,172],[-115.5,164],[-114,58.5]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":33,"s":[{"i":[[11.181,38.599],[0,0],[-18.781,-9.878],[0,0],[31.85,7.078],[-10,42.5]],"o":[[-9.819,62.099],[0,0],[21.915,11.598],[0,0],[-18,-4],[-34.174,-12.829]],"v":[[-215.681,-27.599],[-131.839,62.346],[-140.662,184.505],[-67.5,173],[-116.831,165.664],[-112.003,50.513]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":35,"s":[{"i":[[21,27.5],[0,0],[-18.5,-11],[0,0],[31.85,7.078],[-10,42.5]],"o":[[3,32],[0,0],[22.124,13.155],[0,0],[-18,-4],[-32,-14]],"v":[[-208.5,-54.5],[-130,58.5],[-142,183],[-67.5,172.5],[-117.5,166.5],[-111,46.5]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":38,"s":[{"i":[[20.334,21.177],[0,0],[-19.059,-8.768],[0,0],[31.85,7.078],[-10,42.5]],"o":[[7.657,37.225],[0,0],[21.709,10.057],[0,0],[-18,-4],[-36.326,-11.671]],"v":[[-214.157,-19.225],[-131.997,63.824],[-139.338,185.995],[-67.5,172.5],[-116.169,164.836],[-112.664,53.488]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":40,"s":[{"i":[[20,18],[0,0],[-19.34,-7.646],[0,0],[31.85,7.078],[-10,42.5]],"o":[[15.5,33.5],[0,0],[21.5,8.5],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-217,-1.5],[-133,66.5],[-138,187.5],[-68.5,172.5],[-115.5,164],[-113.5,57]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[17.338,25.654],[0,0],[-18.781,-9.878],[0,0],[31.85,7.078],[-10,42.5]],"o":[[-6.652,46.602],[0,0],[21.915,11.598],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-213.348,-22.602],[-131.669,62.174],[-142.326,182.508],[-66.5,173],[-118.162,163.334],[-109.84,50.344]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":45,"s":[{"i":[[16,29.5],[0,0],[-18.5,-11],[0,0],[31.85,7.078],[-10,42.5]],"o":[[2,37.5],[0,0],[22.124,13.155],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-204,-59.5],[-131,60],[-144.5,180],[-68.5,173],[-119.5,163],[-108,47]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":48,"s":[{"i":[[18.662,21.846],[0,0],[-19.059,-8.768],[0,0],[31.85,7.078],[-10,42.5]],"o":[[10.985,34.838],[0,0],[21.709,10.057],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-215.314,-15.573],[-133.995,66.656],[-140.174,184.992],[-68.008,172.171],[-116.838,163.666],[-111.993,54.654]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":50,"s":[{"i":[[20,18],[0,0],[-19.34,-7.646],[0,0],[31.85,7.078],[-10,42.5]],"o":[[15.5,33.5],[0,0],[21.5,8.5],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-221,6.5],[-135.5,70],[-138,187.5],[-66.5,172],[-115.5,164],[-114,58.5]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":53,"s":[{"i":[[11.181,38.599],[0,0],[-18.781,-9.878],[0,0],[31.85,7.078],[-10,42.5]],"o":[[-9.819,62.099],[0,0],[21.915,11.598],[0,0],[-18,-4],[-34.174,-12.829]],"v":[[-215.681,-27.599],[-131.839,62.346],[-140.662,184.505],[-67.5,173],[-116.831,165.664],[-112.003,50.513]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":55,"s":[{"i":[[21,27.5],[0,0],[-18.5,-11],[0,0],[31.85,7.078],[-10,42.5]],"o":[[3,32],[0,0],[22.124,13.155],[0,0],[-18,-4],[-32,-14]],"v":[[-208.5,-54.5],[-130,58.5],[-142,183],[-67.5,172.5],[-117.5,166.5],[-111,46.5]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":58,"s":[{"i":[[20.334,21.177],[0,0],[-19.059,-8.768],[0,0],[31.85,7.078],[-10,42.5]],"o":[[7.657,37.225],[0,0],[21.709,10.057],[0,0],[-18,-4],[-36.326,-11.671]],"v":[[-214.157,-19.225],[-131.997,63.824],[-139.338,185.995],[-67.5,172.5],[-116.169,164.836],[-112.664,53.488]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":60,"s":[{"i":[[20,18],[0,0],[-19.34,-7.646],[0,0],[31.85,7.078],[-10,42.5]],"o":[[15.5,33.5],[0,0],[21.5,8.5],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-217,-1.5],[-133,66.5],[-138,187.5],[-68.5,172.5],[-115.5,164],[-113.5,57]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":64,"s":[{"i":[[22.473,24.449],[0,0],[-19.046,-8.351],[0,0],[19.512,7.16],[-16.655,43.388]],"o":[[14.055,37.783],[0,0],[21.215,9.302],[0,0],[-17.311,-6.352],[-38.5,-10.5]],"v":[[-211.058,-24.065],[-133,66.5],[-162.263,168.012],[-68.5,172.5],[-130.355,155.795],[-113.5,57]],"c":true}]},{"i":{"x":0.61,"y":1},"o":{"x":0.167,"y":0.167},"t":67,"s":[{"i":[[22.473,24.449],[0,0],[-19.046,-8.351],[0,0],[19.512,7.16],[-16.655,43.388]],"o":[[14.055,37.783],[0,0],[21.215,9.302],[0,0],[-17.311,-6.352],[-38.5,-10.5]],"v":[[-211.058,-24.065],[-133,66.5],[-162.263,168.012],[-68.5,172.5],[-130.355,155.795],[-113.5,57]],"c":true}]},{"i":{"x":0.61,"y":1},"o":{"x":0.41,"y":0},"t":73,"s":[{"i":[[20,18],[0,0],[-19.34,-7.646],[0,0],[31.85,7.078],[-10,42.5]],"o":[[12,26],[0,0],[21.5,8.5],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-218,27.5],[-133,72],[-138,196.5],[-68.5,172.5],[-118,171.5],[-115.5,60.5]],"c":true}]},{"i":{"x":0.61,"y":1},"o":{"x":0.41,"y":0},"t":83,"s":[{"i":[[20,18],[0,0],[-19.34,-7.646],[0,0],[31.85,7.078],[-10,42.5]],"o":[[7,33.24],[0,0],[21.5,8.5],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-215.5,-13.5],[-132.5,63],[-144.5,180.5],[-68.5,172.5],[-120,165],[-113,52.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":89,"s":[{"i":[[20,18],[0,0],[-19.34,-7.646],[0,0],[31.85,7.078],[-10,42.5]],"o":[[15.5,33.5],[0,0],[21.5,8.5],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-217,-1.5],[-133,66.5],[-138,187.5],[-68.5,172.5],[-115.5,164],[-113.5,57]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93,"s":[{"i":[[17.338,25.654],[0,0],[-18.781,-9.878],[0,0],[31.85,7.078],[-10,42.5]],"o":[[-6.652,46.602],[0,0],[21.915,11.598],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-213.348,-22.602],[-131.669,62.174],[-142.326,182.508],[-66.5,173],[-118.162,163.334],[-109.84,50.344]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[16.436,28.248],[0,0],[-18.591,-10.635],[0,0],[31.85,7.078],[-10,42.5]],"o":[[-9.456,51.485],[0,0],[22.056,12.648],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-207.044,-47.485],[-131.218,60.708],[-143.792,180.817],[-68.5,173],[-119.064,163.109],[-108.599,48.089]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":95,"s":[{"i":[[16,29.5],[0,0],[-18.5,-11],[0,0],[31.85,7.078],[-10,42.5]],"o":[[2,37.5],[0,0],[22.124,13.155],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-204,-59.5],[-131,60],[-144.5,180],[-68.5,173],[-119.5,163],[-108,47]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":98,"s":[{"i":[[18.662,21.846],[0,0],[-19.059,-8.768],[0,0],[31.85,7.078],[-10,42.5]],"o":[[10.985,34.838],[0,0],[21.709,10.057],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-215.314,-15.573],[-133.995,66.656],[-140.174,184.992],[-68.008,172.171],[-116.838,163.666],[-111.993,54.654]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":100,"s":[{"i":[[20,18],[0,0],[-19.34,-7.646],[0,0],[31.85,7.078],[-10,42.5]],"o":[[15.5,33.5],[0,0],[21.5,8.5],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-221,6.5],[-135.5,70],[-138,187.5],[-66.5,172],[-115.5,164],[-114,58.5]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":103,"s":[{"i":[[11.181,38.599],[0,0],[-18.781,-9.878],[0,0],[31.85,7.078],[-10,42.5]],"o":[[-9.819,62.099],[0,0],[21.915,11.598],[0,0],[-18,-4],[-34.174,-12.829]],"v":[[-215.681,-27.599],[-131.839,62.346],[-140.662,184.505],[-67.5,173],[-116.831,165.664],[-112.003,50.513]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":105,"s":[{"i":[[21,27.5],[0,0],[-18.5,-11],[0,0],[31.85,7.078],[-10,42.5]],"o":[[3,32],[0,0],[22.124,13.155],[0,0],[-18,-4],[-32,-14]],"v":[[-208.5,-54.5],[-130,58.5],[-142,183],[-67.5,172.5],[-117.5,166.5],[-111,46.5]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":108,"s":[{"i":[[20.334,21.177],[0,0],[-19.059,-8.768],[0,0],[31.85,7.078],[-10,42.5]],"o":[[7.657,37.225],[0,0],[21.709,10.057],[0,0],[-18,-4],[-36.326,-11.671]],"v":[[-214.157,-19.225],[-131.997,63.824],[-139.338,185.995],[-67.5,172.5],[-116.169,164.836],[-112.664,53.488]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":110,"s":[{"i":[[20,18],[0,0],[-19.34,-7.646],[0,0],[31.85,7.078],[-10,42.5]],"o":[[15.5,33.5],[0,0],[21.5,8.5],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-217,-1.5],[-133,66.5],[-138,187.5],[-68.5,172.5],[-115.5,164],[-113.5,57]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":113,"s":[{"i":[[17.338,25.654],[0,0],[-18.781,-9.878],[0,0],[31.85,7.078],[-10,42.5]],"o":[[-6.652,46.602],[0,0],[21.915,11.598],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-213.348,-22.602],[-131.669,62.174],[-142.326,182.508],[-66.5,173],[-118.162,163.334],[-109.84,50.344]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":114,"s":[{"i":[[16.436,28.248],[0,0],[-18.591,-10.635],[0,0],[31.85,7.078],[-10,42.5]],"o":[[-9.456,51.485],[0,0],[22.056,12.648],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-207.044,-47.485],[-131.218,60.708],[-143.792,180.817],[-68.5,173],[-119.064,163.109],[-108.599,48.089]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":115,"s":[{"i":[[16,29.5],[0,0],[-18.5,-11],[0,0],[31.85,7.078],[-10,42.5]],"o":[[2,37.5],[0,0],[22.124,13.155],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-204,-59.5],[-131,60],[-144.5,180],[-68.5,173],[-119.5,163],[-108,47]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[18.662,21.846],[0,0],[-19.059,-8.768],[0,0],[31.85,7.078],[-10,42.5]],"o":[[10.985,34.838],[0,0],[21.709,10.057],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-215.314,-15.573],[-133.995,66.656],[-140.174,184.992],[-68.008,172.171],[-116.838,163.666],[-111.993,54.654]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":120,"s":[{"i":[[20,18],[0,0],[-19.34,-7.646],[0,0],[31.85,7.078],[-10,42.5]],"o":[[15.5,33.5],[0,0],[21.5,8.5],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-221,6.5],[-135.5,70],[-138,187.5],[-66.5,172],[-115.5,164],[-114,58.5]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":123,"s":[{"i":[[11.181,38.599],[0,0],[-18.781,-9.878],[0,0],[31.85,7.078],[-10,42.5]],"o":[[-9.819,62.099],[0,0],[21.915,11.598],[0,0],[-18,-4],[-34.174,-12.829]],"v":[[-215.681,-27.599],[-131.839,62.346],[-140.662,184.505],[-67.5,173],[-116.831,165.664],[-112.003,50.513]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":125,"s":[{"i":[[21,27.5],[0,0],[-18.5,-11],[0,0],[31.85,7.078],[-10,42.5]],"o":[[3,32],[0,0],[22.124,13.155],[0,0],[-18,-4],[-32,-14]],"v":[[-208.5,-54.5],[-130,58.5],[-142,183],[-67.5,172.5],[-117.5,166.5],[-111,46.5]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":128,"s":[{"i":[[20.334,21.177],[0,0],[-19.059,-8.768],[0,0],[31.85,7.078],[-10,42.5]],"o":[[7.657,37.225],[0,0],[21.709,10.057],[0,0],[-18,-4],[-36.326,-11.671]],"v":[[-214.157,-19.225],[-131.997,63.824],[-139.338,185.995],[-67.5,172.5],[-116.169,164.836],[-112.664,53.488]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":130,"s":[{"i":[[20,18],[0,0],[-19.34,-7.646],[0,0],[31.85,7.078],[-10,42.5]],"o":[[15.5,33.5],[0,0],[21.5,8.5],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-217,-1.5],[-133,66.5],[-138,187.5],[-68.5,172.5],[-115.5,164],[-113.5,57]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":133,"s":[{"i":[[17.338,25.654],[0,0],[-18.781,-9.878],[0,0],[31.85,7.078],[-10,42.5]],"o":[[-6.652,46.602],[0,0],[21.915,11.598],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-213.348,-22.602],[-131.669,62.174],[-142.326,182.508],[-66.5,173],[-118.162,163.334],[-109.84,50.344]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":135,"s":[{"i":[[16,29.5],[0,0],[-18.5,-11],[0,0],[31.85,7.078],[-10,42.5]],"o":[[2,37.5],[0,0],[22.124,13.155],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-204,-59.5],[-131,60],[-144.5,180],[-68.5,173],[-119.5,163],[-108,47]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":138,"s":[{"i":[[18.662,21.846],[0,0],[-19.059,-8.768],[0,0],[31.85,7.078],[-10,42.5]],"o":[[10.985,34.838],[0,0],[21.709,10.057],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-215.314,-15.573],[-133.995,66.656],[-140.174,184.992],[-68.008,172.171],[-116.838,163.666],[-111.993,54.654]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":140,"s":[{"i":[[20,18],[0,0],[-19.34,-7.646],[0,0],[31.85,7.078],[-10,42.5]],"o":[[15.5,33.5],[0,0],[21.5,8.5],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-221,6.5],[-135.5,70],[-138,187.5],[-66.5,172],[-115.5,164],[-114,58.5]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":143,"s":[{"i":[[11.181,38.599],[0,0],[-18.781,-9.878],[0,0],[31.85,7.078],[-10,42.5]],"o":[[-9.819,62.099],[0,0],[21.915,11.598],[0,0],[-18,-4],[-34.174,-12.829]],"v":[[-215.681,-27.599],[-131.839,62.346],[-140.662,184.505],[-67.5,173],[-116.831,165.664],[-112.003,50.513]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":145,"s":[{"i":[[21,27.5],[0,0],[-18.5,-11],[0,0],[31.85,7.078],[-10,42.5]],"o":[[3,32],[0,0],[22.124,13.155],[0,0],[-18,-4],[-32,-14]],"v":[[-208.5,-54.5],[-130,58.5],[-142,183],[-67.5,172.5],[-117.5,166.5],[-111,46.5]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":148,"s":[{"i":[[20.334,21.177],[0,0],[-19.059,-8.768],[0,0],[31.85,7.078],[-10,42.5]],"o":[[7.657,37.225],[0,0],[21.709,10.057],[0,0],[-18,-4],[-36.326,-11.671]],"v":[[-214.157,-19.225],[-131.997,63.824],[-139.338,185.995],[-67.5,172.5],[-116.169,164.836],[-112.664,53.488]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":150,"s":[{"i":[[20,18],[0,0],[-19.34,-7.646],[0,0],[31.85,7.078],[-10,42.5]],"o":[[15.5,33.5],[0,0],[21.5,8.5],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-217,-1.5],[-133,66.5],[-138,187.5],[-68.5,172.5],[-115.5,164],[-113.5,57]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":154,"s":[{"i":[[22.473,24.449],[0,0],[-19.046,-8.351],[0,0],[19.512,7.16],[-16.655,43.388]],"o":[[14.055,37.783],[0,0],[21.215,9.302],[0,0],[-17.311,-6.352],[-38.5,-10.5]],"v":[[-211.058,-24.065],[-133,66.5],[-162.263,168.012],[-68.5,172.5],[-130.355,155.795],[-113.5,57]],"c":true}]},{"i":{"x":0.61,"y":1},"o":{"x":0.167,"y":0.167},"t":157,"s":[{"i":[[22.473,24.449],[0,0],[-19.046,-8.351],[0,0],[19.512,7.16],[-16.655,43.388]],"o":[[14.055,37.783],[0,0],[21.215,9.302],[0,0],[-17.311,-6.352],[-38.5,-10.5]],"v":[[-211.058,-24.065],[-133,66.5],[-162.263,168.012],[-68.5,172.5],[-130.355,155.795],[-113.5,57]],"c":true}]},{"i":{"x":0.61,"y":1},"o":{"x":0.41,"y":0},"t":163,"s":[{"i":[[20,18],[0,0],[-19.34,-7.646],[0,0],[31.85,7.078],[-10,42.5]],"o":[[12,26],[0,0],[21.5,8.5],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-218,27.5],[-133,72],[-138,196.5],[-68.5,172.5],[-118,171.5],[-115.5,60.5]],"c":true}]},{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":173,"s":[{"i":[[20,18],[0,0],[-19.34,-7.646],[0,0],[31.85,7.078],[-10,42.5]],"o":[[7,33.24],[0,0],[21.5,8.5],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-215.5,-13.5],[-132.5,63],[-144.5,180.5],[-68.5,172.5],[-120,165],[-113,52.5]],"c":true}]},{"t":179,"s":[{"i":[[20,18],[0,0],[-19.34,-7.646],[0,0],[31.85,7.078],[-10,42.5]],"o":[[15.5,33.5],[0,0],[21.5,8.5],[0,0],[-18,-4],[-38.5,-10.5]],"v":[[-217,-1.5],[-133,66.5],[-138,187.5],[-68.5,172.5],[-115.5,164],[-113.5,57]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.662745098039,0.282352941176,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.960784373564,0.694117647059,0.152941176471,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":0,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.158,6.145],[0,0],[-2,-42.5],[0,0],[14,-11.5],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[40,-5],[0,0],[1.46,31.03],[0,0],[-18.706,15.365],[0,0]],"v":[[-67,178],[-144,187.5],[-138,68.5],[-222.5,-12],[-80.5,-70],[8.5,-194.5],[99.5,-63.5],[226.5,0.5],[143.5,80.5],[165.5,180],[82,176.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":3,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-20.316,69.435],[0,0],[-49.158,6.145],[0,0],[-12.1,-30.041],[0,0],[11.338,-15.161],[0,0]],"o":[[0,0],[-17,-14],[0,0],[20.184,-52.565],[0,0],[40,-5],[0,0],[25.833,76.256],[0,0],[-14.425,19.026],[0,0]],"v":[[-67,178],[-147.993,183.507],[-137.002,64.174],[-217.184,-43.935],[-78.171,-73.328],[14.823,-198.161],[98.169,-67.493],[226.167,-45.756],[146.828,73.512],[169.826,175.008],[81.334,174.836]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-21.684,66.85],[0,0],[-49.158,6.145],[0,0],[-15.522,-25.819],[0,0],[10.436,-16.401],[0,0]],"o":[[0,0],[-17,-14],[0,0],[16.363,-33.241],[0,0],[40,-5],[0,0],[25.946,46.431],[0,0],[-12.974,20.266],[0,0]],"v":[[-67,178],[-149.347,182.153],[-136.663,62.708],[-210.316,-61.85],[-77.381,-74.455],[16.965,-199.401],[97.718,-68.847],[226.054,-61.431],[147.956,71.143],[171.292,173.317],[81.109,174.272]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":5,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-17,28],[0,0],[-49.158,6.145],[0,0],[-17.175,-23.781],[0,0],[10,-17],[0,0]],"o":[[0,0],[-17,-14],[0,0],[14.517,-23.911],[0,0],[40,-5],[0,0],[19.5,27],[0,0],[-12.274,20.865],[0,0]],"v":[[-67,178],[-150,181.5],[-136.5,62],[-207,-70.5],[-77,-75],[18,-200],[97.5,-69.5],[226,-69],[148.5,70],[172,172.5],[81,174]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-10.685,32.799],[0,0],[-49.352,4.05],[0,0],[-8.833,-66.253],[0,0],[12.662,-13.339],[0,0]],"o":[[0,0],[-17,-14],[0,0],[17.313,-66.092],[0,0],[35.341,-3.003],[0,0],[5.931,29.697],[0,0],[-16.555,17.205],[0,0]],"v":[[-67,178],[-146.007,185.493],[-138.829,68.988],[-219.313,-25.908],[-79.329,-71.672],[17.002,-195.341],[99.497,-65.174],[226.333,-20.747],[145.172,81.314],[167.674,177.492],[81.666,175.664]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":10,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.45,2.997],[0,0],[1,-35],[0,0],[14,-11.5],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[33,-2],[0,0],[-0.887,31.052],[0,0],[-18.706,15.365],[0,0]],"v":[[-67,178],[-144,187.5],[-140,72.5],[-225.5,-3.5],[-80.5,-70],[16.5,-193],[100.5,-63],[226.5,3.5],[143.5,87],[165.5,180],[82,176.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":13,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-26.152,78.596],[0,0],[-49.255,5.092],[0,0],[-11.096,-27.533],[0,0],[11.338,-15.161],[0,0]],"o":[[0,0],[-17,-14],[0,0],[12.338,-28.456],[0,0],[37.659,-3.997],[0,0],[20.664,87.082],[0,0],[-14.425,19.026],[0,0]],"v":[[-67,178],[-145.997,186.169],[-137.005,64.181],[-216.848,-46.096],[-78.503,-72.329],[5.851,-195.995],[98.503,-67.326],[224.836,-47.082],[148.824,73.356],[172.156,173.677],[81.334,174.836]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":15,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-17,28],[0,0],[-49.158,6.145],[0,0],[-17.175,-23.781],[0,0],[10,-17],[0,0]],"o":[[0,0],[-17,-14],[0,0],[14.517,-23.911],[0,0],[40,-5],[0,0],[19.5,27],[0,0],[-12.274,20.865],[0,0]],"v":[[-67,178],[-147,185.5],[-135.5,60],[-212.5,-67.5],[-77.5,-73.5],[0.5,-197.5],[97.5,-69.5],[224,-72.5],[151.5,66.5],[175.5,170.5],[81,174]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-11.844,41.561],[0,0],[-49.158,6.145],[0,0],[-16.164,-70.086],[0,0],[12.662,-13.339],[0,0]],"o":[[0,0],[-17,-14],[0,0],[17.156,-68.439],[0,0],[40,-5],[0,0],[7.493,29.682],[0,0],[-16.555,17.205],[0,0]],"v":[[-67,178],[-145.003,186.831],[-137.164,65.657],[-219.156,-30.561],[-79.497,-71.171],[5.824,-195.503],[98.831,-65.507],[225.664,-23.914],[146.176,75.818],[168.844,176.823],[81.666,175.664]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":20,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.158,6.145],[0,0],[-2,-42.5],[0,0],[14,-11.5],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[40,-5],[0,0],[1.46,31.03],[0,0],[-18.706,15.365],[0,0]],"v":[[-67,178],[-144,187.5],[-138,68.5],[-222.5,-12],[-81.495,-78.095],[8.5,-194.5],[100.495,-71.595],[226.5,0.5],[143.5,80.5],[165.5,180],[82,176.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":23,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-20.316,69.435],[0,0],[-49.158,6.145],[0,0],[-12.1,-30.041],[0,0],[11.338,-15.161],[0,0]],"o":[[0,0],[-17,-14],[0,0],[20.184,-52.565],[0,0],[40,-5],[0,0],[25.833,76.256],[0,0],[-14.425,19.026],[0,0]],"v":[[-67,178],[-147.993,183.507],[-137.002,64.174],[-217.184,-43.935],[-78.171,-73.328],[14.823,-198.161],[98.169,-67.493],[226.167,-45.756],[146.828,73.512],[169.826,175.008],[81.334,174.836]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-21.684,66.85],[0,0],[-49.158,6.145],[0,0],[-15.522,-25.819],[0,0],[10.436,-16.401],[0,0]],"o":[[0,0],[-17,-14],[0,0],[16.363,-33.241],[0,0],[40,-5],[0,0],[25.946,46.431],[0,0],[-12.974,20.266],[0,0]],"v":[[-67,178],[-149.347,182.153],[-136.663,62.708],[-210.316,-61.85],[-77.381,-74.455],[16.965,-199.401],[97.718,-68.847],[226.054,-61.431],[147.956,71.143],[171.292,173.317],[81.109,174.272]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":25,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-17,28],[0,0],[-49.158,6.145],[0,0],[-17.175,-23.781],[0,0],[10,-17],[0,0]],"o":[[0,0],[-17,-14],[0,0],[14.517,-23.911],[0,0],[40,-5],[0,0],[19.5,27],[0,0],[-12.274,20.865],[0,0]],"v":[[-67,178],[-150,181.5],[-136.5,62],[-207,-70.5],[-77,-85.184],[18,-200],[99.486,-81.721],[226,-69],[148.5,70],[172,172.5],[81,174]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-10.685,32.799],[0,0],[-49.352,4.05],[0,0],[-8.833,-66.253],[0,0],[12.662,-13.339],[0,0]],"o":[[0,0],[-17,-14],[0,0],[17.313,-66.092],[0,0],[35.341,-3.003],[0,0],[5.931,29.697],[0,0],[-16.555,17.205],[0,0]],"v":[[-67,178],[-146.007,185.493],[-138.829,68.988],[-219.313,-25.908],[-79.825,-83.945],[17.002,-195.341],[99.992,-80.003],[226.333,-20.747],[145.172,81.314],[167.674,177.492],[81.666,175.664]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":30,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.45,2.997],[0,0],[1,-35],[0,0],[14,-11.5],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[33,-2],[0,0],[-0.887,31.052],[0,0],[-18.706,15.365],[0,0]],"v":[[-67,178],[-144,187.5],[-140,72.5],[-225.5,-3.5],[-84.461,-85.898],[15.014,-179.153],[100.995,-77.872],[226.5,3.5],[143.5,87],[165.5,180],[82,176.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":33,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-26.152,78.596],[0,0],[-49.255,5.092],[0,0],[-11.096,-27.533],[0,0],[11.338,-15.161],[0,0]],"o":[[0,0],[-17,-14],[0,0],[12.338,-28.456],[0,0],[37.659,-3.997],[0,0],[20.664,87.083],[0,0],[-14.425,19.026],[0,0]],"v":[[-67,178],[-145.997,186.169],[-137.005,64.181],[-216.848,-46.096],[-78.998,-92.417],[4.862,-177.968],[96.526,-84.838],[224.836,-47.082],[148.824,73.356],[172.156,173.677],[81.334,174.836]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":35,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-17,28],[0,0],[-49.158,6.145],[0,0],[-17.175,-23.781],[0,0],[10,-17],[0,0]],"o":[[0,0],[-17,-14],[0,0],[14.517,-23.911],[0,0],[40,-5],[0,0],[19.5,27],[0,0],[-12.274,20.865],[0,0]],"v":[[-67,178],[-147,185.5],[-135.5,60],[-212.5,-67.5],[-77.5,-95.711],[2.969,-175.289],[95.525,-88.611],[224,-72.5],[151.5,66.5],[175.5,170.5],[81,174]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":38,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-11.844,41.561],[0,0],[-49.158,6.145],[0,0],[-16.164,-70.086],[0,0],[12.662,-13.339],[0,0]],"o":[[0,0],[-17,-14],[0,0],[17.156,-68.439],[0,0],[40,-5],[0,0],[7.493,29.682],[0,0],[-16.555,17.205],[0,0]],"v":[[-67,178],[-145.003,186.831],[-137.164,65.657],[-219.156,-30.561],[-78.51,-91.396],[4.345,-177.87],[93.9,-92.474],[225.664,-23.914],[146.176,75.818],[168.844,176.823],[81.666,175.664]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":40,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.158,6.145],[0,0],[-2,-42.5],[0,0],[14,-11.5],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[40,-5],[0,0],[1.46,31.03],[0,0],[-18.706,15.365],[0,0]],"v":[[-67,178],[-144,187.5],[-138,68.5],[-222.5,-12],[-81.485,-90.796],[5.544,-174.223],[100.485,-81.177],[226.5,0.5],[143.5,80.5],[165.5,180],[82,176.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-20.316,69.435],[0,0],[-49.158,6.145],[0,0],[-12.1,-30.041],[0,0],[11.338,-15.161],[0,0]],"o":[[0,0],[-17,-14],[0,0],[20.184,-52.565],[0,0],[40,-5],[0,0],[25.833,76.256],[0,0],[-14.425,19.026],[0,0]],"v":[[-67,178],[-147.993,183.507],[-137.002,64.174],[-217.184,-43.935],[-75.71,-88.978],[10.394,-176.25],[97.185,-89.404],[226.167,-45.756],[146.828,73.512],[169.826,175.008],[81.334,174.836]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":45,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-17,28],[0,0],[-49.158,6.145],[0,0],[-17.175,-23.781],[0,0],[10,-17],[0,0]],"o":[[0,0],[-17,-14],[0,0],[14.517,-23.911],[0,0],[40,-5],[0,0],[19.5,27],[0,0],[-12.274,20.865],[0,0]],"v":[[-67,178],[-150,181.5],[-136.5,62],[-207,-70.5],[-76.017,-97.999],[11.608,-174.91],[97.008,-87.272],[226,-69],[148.5,70],[172,172.5],[81,174]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":48,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-10.685,32.799],[0,0],[-49.352,4.05],[0,0],[-8.833,-66.253],[0,0],[12.662,-13.339],[0,0]],"o":[[0,0],[-17,-14],[0,0],[17.313,-66.092],[0,0],[35.341,-3.003],[0,0],[5.931,29.697],[0,0],[-16.555,17.205],[0,0]],"v":[[-67,178],[-146.007,185.493],[-138.829,68.988],[-219.313,-25.908],[-79.329,-92.109],[10.616,-167.043],[98.514,-89.803],[226.333,-20.747],[145.172,81.314],[167.674,177.492],[81.666,175.664]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":50,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.262,5.247],[0,0],[1,-35],[0,0],[14,-11.5],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[45.995,-4.899],[0,0],[-0.887,31.052],[0,0],[-18.706,15.365],[0,0]],"v":[[-67,178],[-144,187.5],[-140,72.5],[-225.5,-3.5],[-79.518,-90.465],[11.59,-162.564],[97.554,-81.366],[226.5,3.5],[143.5,87],[165.5,180],[82,176.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":53,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-26.152,78.596],[0,0],[-49.255,5.092],[0,0],[-11.096,-27.533],[0,0],[11.338,-15.161],[0,0]],"o":[[0,0],[-17,-14],[0,0],[13.353,-39.632],[0,0],[43.855,-3.783],[0,0],[20.664,87.082],[0,0],[-14.425,19.026],[0,0]],"v":[[-67,178],[-145.997,186.169],[-137.005,64.18],[-216.848,-46.096],[-76.05,-97.557],[6.342,-167.614],[93.106,-90.977],[224.836,-47.082],[148.824,73.356],[172.156,173.677],[81.334,174.836]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":55,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-17,28],[0,0],[-49.179,5.975],[0,0],[-17.175,-23.781],[0,0],[10,-17],[0,0]],"o":[[0,0],[-17,-14],[0,0],[14.517,-23.911],[0,0],[48.213,-5.858],[0,0],[19.5,27],[0,0],[-12.274,20.865],[0,0]],"v":[[-67,178],[-147,185.5],[-135.5,60],[-212.5,-67.5],[-73.085,-99.798],[2.462,-172.78],[95.047,-87.383],[224,-72.5],[151.5,66.5],[175.5,170.5],[81,174]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":58,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-11.844,41.562],[0,0],[-49.158,6.145],[0,0],[-16.164,-70.086],[0,0],[12.662,-13.339],[0,0]],"o":[[0,0],[-17,-14],[0,0],[17.156,-68.439],[0,0],[40,-5],[0,0],[7.493,29.682],[0,0],[-16.555,17.205],[0,0]],"v":[[-67,178],[-145.003,186.831],[-137.164,65.657],[-219.156,-30.561],[-76.554,-96.959],[6.315,-178.136],[96.379,-83.927],[225.664,-23.914],[146.176,75.818],[168.844,176.823],[81.666,175.664]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":60,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.158,6.145],[0,0],[-2,-42.5],[0,0],[14,-11.5],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[40,-5],[0,0],[1.46,31.03],[0,0],[-18.706,15.365],[0,0]],"v":[[-67,178],[-144,187.5],[-138,68.5],[-222.5,-12],[-80.5,-70],[8.5,-194.5],[99.5,-63.5],[226.5,0.5],[143.5,80.5],[165.5,180],[82,176.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":64,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.158,6.145],[0,0],[-2,-42.5],[0,0],[7.39,-22.687],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[40,-5],[0,0],[1.46,31.03],[0,0],[-7.498,23.017],[0,0]],"v":[[-67,178],[-167.273,169.551],[-138,68.5],[-216.558,-37.642],[-80.5,-70],[8.5,-194.5],[99.5,-63.5],[225.014,-20.014],[143.5,80.5],[180.85,166.153],[82,176.5]],"c":false}]},{"i":{"x":0.61,"y":1},"o":{"x":0.167,"y":0.167},"t":67,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.158,6.145],[0,0],[-2,-42.5],[0,0],[7.39,-22.687],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[40,-5],[0,0],[1.46,31.03],[0,0],[-7.498,23.017],[0,0]],"v":[[-67,178],[-167.273,169.551],[-138,68.5],[-216.558,-37.642],[-80.5,-70],[8.5,-194.5],[99.5,-63.5],[225.014,-20.014],[143.5,80.5],[180.85,166.153],[82,176.5]],"c":false}]},{"i":{"x":0.61,"y":1},"o":{"x":0.41,"y":0},"t":73,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-52.5,1],[0,0],[-2,-42.5],[0,0],[14,-11.5],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[49.615,-0.945],[0,0],[1.46,31.03],[0,0],[-18.706,15.365],[0,0]],"v":[[-67,178],[-144.5,198.5],[-139,75],[-224.5,16.5],[-79.5,-66],[9,-188],[101,-58.5],[227,24.5],[143.5,84.5],[155.5,195],[82,176.5]],"c":false}]},{"i":{"x":0.61,"y":1},"o":{"x":0.41,"y":0},"t":83,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.158,6.145],[0,0],[-2,-42.5],[0,0],[14,-11.5],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[40,-5],[0,0],[1.46,31.03],[0,0],[-18.706,15.365],[0,0]],"v":[[-67,178],[-150,182],[-137.5,63.5],[-223.5,-26.5],[-80.5,-75],[9,-197],[99,-66.5],[227,-11.5],[143.5,80.5],[165.5,180],[82,176.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":89,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.158,6.145],[0,0],[-2,-42.5],[0,0],[14,-11.5],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[40,-5],[0,0],[1.46,31.03],[0,0],[-18.706,15.365],[0,0]],"v":[[-67,178],[-144,187.5],[-138,68.5],[-222.5,-12],[-80.5,-70],[8.5,-194.5],[99.5,-63.5],[226.5,0.5],[143.5,80.5],[165.5,180],[82,176.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-20.316,69.435],[0,0],[-49.158,6.145],[0,0],[-12.1,-30.041],[0,0],[11.338,-15.161],[0,0]],"o":[[0,0],[-17,-14],[0,0],[20.184,-52.565],[0,0],[40,-5],[0,0],[25.833,76.256],[0,0],[-14.425,19.026],[0,0]],"v":[[-67,178],[-147.993,183.507],[-137.002,64.174],[-217.184,-43.935],[-78.171,-73.328],[14.823,-198.161],[98.169,-67.493],[226.167,-45.756],[146.828,73.512],[169.826,175.008],[81.334,174.836]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-21.684,66.85],[0,0],[-49.158,6.145],[0,0],[-15.522,-25.819],[0,0],[10.436,-16.401],[0,0]],"o":[[0,0],[-17,-14],[0,0],[16.363,-33.241],[0,0],[40,-5],[0,0],[25.946,46.431],[0,0],[-12.974,20.266],[0,0]],"v":[[-67,178],[-149.347,182.153],[-136.663,62.708],[-210.316,-61.85],[-77.381,-74.455],[16.965,-199.401],[97.718,-68.847],[226.054,-61.431],[147.956,71.143],[171.292,173.317],[81.109,174.272]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":95,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-17,28],[0,0],[-49.158,6.145],[0,0],[-17.175,-23.781],[0,0],[10,-17],[0,0]],"o":[[0,0],[-17,-14],[0,0],[14.517,-23.911],[0,0],[40,-5],[0,0],[19.5,27],[0,0],[-12.274,20.865],[0,0]],"v":[[-67,178],[-150,181.5],[-136.5,62],[-207,-70.5],[-77,-75],[18,-200],[97.5,-69.5],[226,-69],[148.5,70],[172,172.5],[81,174]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":98,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-10.685,32.799],[0,0],[-49.352,4.05],[0,0],[-8.833,-66.253],[0,0],[12.662,-13.339],[0,0]],"o":[[0,0],[-17,-14],[0,0],[17.313,-66.092],[0,0],[35.341,-3.003],[0,0],[5.931,29.697],[0,0],[-16.555,17.205],[0,0]],"v":[[-67,178],[-146.007,185.493],[-138.829,68.988],[-219.313,-25.908],[-79.329,-71.672],[17.002,-195.341],[99.497,-65.174],[226.333,-20.747],[145.172,81.314],[167.674,177.492],[81.666,175.664]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":100,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.45,2.997],[0,0],[1,-35],[0,0],[14,-11.5],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[33,-2],[0,0],[-0.887,31.052],[0,0],[-18.706,15.365],[0,0]],"v":[[-67,178],[-144,187.5],[-140,72.5],[-225.5,-3.5],[-80.5,-70],[16.5,-193],[100.5,-63],[226.5,3.5],[143.5,87],[165.5,180],[82,176.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":103,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-26.152,78.596],[0,0],[-49.255,5.092],[0,0],[-11.096,-27.533],[0,0],[11.338,-15.161],[0,0]],"o":[[0,0],[-17,-14],[0,0],[12.338,-28.456],[0,0],[37.659,-3.997],[0,0],[20.664,87.082],[0,0],[-14.425,19.026],[0,0]],"v":[[-67,178],[-145.997,186.169],[-137.005,64.181],[-216.848,-46.096],[-78.503,-72.329],[5.851,-195.995],[98.503,-67.326],[224.836,-47.082],[148.824,73.356],[172.156,173.677],[81.334,174.836]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":105,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-17,28],[0,0],[-49.158,6.145],[0,0],[-17.175,-23.781],[0,0],[10,-17],[0,0]],"o":[[0,0],[-17,-14],[0,0],[14.517,-23.911],[0,0],[40,-5],[0,0],[19.5,27],[0,0],[-12.274,20.865],[0,0]],"v":[[-67,178],[-147,185.5],[-135.5,60],[-212.5,-67.5],[-77.5,-73.5],[0.5,-197.5],[97.5,-69.5],[224,-72.5],[151.5,66.5],[175.5,170.5],[81,174]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":108,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-11.844,41.561],[0,0],[-49.158,6.145],[0,0],[-16.164,-70.086],[0,0],[12.662,-13.339],[0,0]],"o":[[0,0],[-17,-14],[0,0],[17.156,-68.439],[0,0],[40,-5],[0,0],[7.493,29.682],[0,0],[-16.555,17.205],[0,0]],"v":[[-67,178],[-145.003,186.831],[-137.164,65.657],[-219.156,-30.561],[-79.497,-71.171],[5.824,-195.503],[98.831,-65.507],[225.664,-23.914],[146.176,75.818],[168.844,176.823],[81.666,175.664]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":110,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.158,6.145],[0,0],[-2,-42.5],[0,0],[14,-11.5],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[40,-5],[0,0],[1.46,31.03],[0,0],[-18.706,15.365],[0,0]],"v":[[-67,178],[-144,187.5],[-138,68.5],[-222.5,-12],[-81.495,-78.095],[8.5,-194.5],[100.495,-71.595],[226.5,0.5],[143.5,80.5],[165.5,180],[82,176.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":113,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-20.316,69.435],[0,0],[-49.158,6.145],[0,0],[-12.1,-30.041],[0,0],[11.338,-15.161],[0,0]],"o":[[0,0],[-17,-14],[0,0],[20.184,-52.565],[0,0],[40,-5],[0,0],[25.833,76.256],[0,0],[-14.425,19.026],[0,0]],"v":[[-67,178],[-147.993,183.507],[-137.002,64.174],[-217.184,-43.935],[-78.171,-73.328],[14.823,-198.161],[98.169,-67.493],[226.167,-45.756],[146.828,73.512],[169.826,175.008],[81.334,174.836]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":114,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-21.684,66.85],[0,0],[-49.158,6.145],[0,0],[-15.522,-25.819],[0,0],[10.436,-16.401],[0,0]],"o":[[0,0],[-17,-14],[0,0],[16.363,-33.241],[0,0],[40,-5],[0,0],[25.946,46.431],[0,0],[-12.974,20.266],[0,0]],"v":[[-67,178],[-149.347,182.153],[-136.663,62.708],[-210.316,-61.85],[-77.381,-74.455],[16.965,-199.401],[97.718,-68.847],[226.054,-61.431],[147.956,71.143],[171.292,173.317],[81.109,174.272]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":115,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-17,28],[0,0],[-49.158,6.145],[0,0],[-17.175,-23.781],[0,0],[10,-17],[0,0]],"o":[[0,0],[-17,-14],[0,0],[14.517,-23.911],[0,0],[40,-5],[0,0],[19.5,27],[0,0],[-12.274,20.865],[0,0]],"v":[[-67,178],[-150,181.5],[-136.5,62],[-207,-70.5],[-77,-85.184],[18,-200],[99.486,-81.721],[226,-69],[148.5,70],[172,172.5],[81,174]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-10.685,32.799],[0,0],[-49.352,4.05],[0,0],[-8.833,-66.253],[0,0],[12.662,-13.339],[0,0]],"o":[[0,0],[-17,-14],[0,0],[17.313,-66.092],[0,0],[35.341,-3.003],[0,0],[5.931,29.697],[0,0],[-16.555,17.205],[0,0]],"v":[[-67,178],[-146.007,185.493],[-138.829,68.988],[-219.313,-25.908],[-79.825,-83.945],[17.002,-195.341],[99.992,-80.003],[226.333,-20.747],[145.172,81.314],[167.674,177.492],[81.666,175.664]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":120,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.45,2.997],[0,0],[1,-35],[0,0],[14,-11.5],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[33,-2],[0,0],[-0.887,31.052],[0,0],[-18.706,15.365],[0,0]],"v":[[-67,178],[-144,187.5],[-140,72.5],[-225.5,-3.5],[-84.461,-85.898],[15.014,-179.153],[100.995,-77.872],[226.5,3.5],[143.5,87],[165.5,180],[82,176.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":123,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-26.152,78.596],[0,0],[-49.255,5.092],[0,0],[-11.096,-27.533],[0,0],[11.338,-15.161],[0,0]],"o":[[0,0],[-17,-14],[0,0],[12.338,-28.456],[0,0],[37.659,-3.997],[0,0],[20.664,87.083],[0,0],[-14.425,19.026],[0,0]],"v":[[-67,178],[-145.997,186.169],[-137.005,64.181],[-216.848,-46.096],[-78.998,-92.417],[4.862,-177.968],[96.526,-84.838],[224.836,-47.082],[148.824,73.356],[172.156,173.677],[81.334,174.836]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":125,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-17,28],[0,0],[-49.158,6.145],[0,0],[-17.175,-23.781],[0,0],[10,-17],[0,0]],"o":[[0,0],[-17,-14],[0,0],[14.517,-23.911],[0,0],[40,-5],[0,0],[19.5,27],[0,0],[-12.274,20.865],[0,0]],"v":[[-67,178],[-147,185.5],[-135.5,60],[-212.5,-67.5],[-77.5,-95.711],[2.969,-175.289],[95.525,-88.611],[224,-72.5],[151.5,66.5],[175.5,170.5],[81,174]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":128,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-11.844,41.561],[0,0],[-49.158,6.145],[0,0],[-16.164,-70.086],[0,0],[12.662,-13.339],[0,0]],"o":[[0,0],[-17,-14],[0,0],[17.156,-68.439],[0,0],[40,-5],[0,0],[7.493,29.682],[0,0],[-16.555,17.205],[0,0]],"v":[[-67,178],[-145.003,186.831],[-137.164,65.657],[-219.156,-30.561],[-78.51,-91.396],[4.345,-177.87],[93.9,-92.474],[225.664,-23.914],[146.176,75.818],[168.844,176.823],[81.666,175.664]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":130,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.158,6.145],[0,0],[-2,-42.5],[0,0],[14,-11.5],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[40,-5],[0,0],[1.46,31.03],[0,0],[-18.706,15.365],[0,0]],"v":[[-67,178],[-144,187.5],[-138,68.5],[-222.5,-12],[-81.485,-90.796],[5.544,-174.223],[100.485,-81.177],[226.5,0.5],[143.5,80.5],[165.5,180],[82,176.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":133,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-20.316,69.435],[0,0],[-49.158,6.145],[0,0],[-12.1,-30.041],[0,0],[11.338,-15.161],[0,0]],"o":[[0,0],[-17,-14],[0,0],[20.184,-52.565],[0,0],[40,-5],[0,0],[25.833,76.256],[0,0],[-14.425,19.026],[0,0]],"v":[[-67,178],[-147.993,183.507],[-137.002,64.174],[-217.184,-43.935],[-75.71,-88.978],[10.394,-176.25],[97.185,-89.404],[226.167,-45.756],[146.828,73.512],[169.826,175.008],[81.334,174.836]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":135,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-17,28],[0,0],[-49.158,6.145],[0,0],[-17.175,-23.781],[0,0],[10,-17],[0,0]],"o":[[0,0],[-17,-14],[0,0],[14.517,-23.911],[0,0],[40,-5],[0,0],[19.5,27],[0,0],[-12.274,20.865],[0,0]],"v":[[-67,178],[-150,181.5],[-136.5,62],[-207,-70.5],[-76.017,-97.999],[11.608,-174.91],[97.008,-87.272],[226,-69],[148.5,70],[172,172.5],[81,174]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":138,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-10.685,32.799],[0,0],[-49.352,4.05],[0,0],[-8.833,-66.253],[0,0],[12.662,-13.339],[0,0]],"o":[[0,0],[-17,-14],[0,0],[17.313,-66.092],[0,0],[35.341,-3.003],[0,0],[5.931,29.697],[0,0],[-16.555,17.205],[0,0]],"v":[[-67,178],[-146.007,185.493],[-138.829,68.988],[-219.313,-25.908],[-79.329,-92.109],[10.616,-167.043],[98.514,-89.803],[226.333,-20.747],[145.172,81.314],[167.674,177.492],[81.666,175.664]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":140,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.262,5.247],[0,0],[1,-35],[0,0],[14,-11.5],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[45.995,-4.899],[0,0],[-0.887,31.052],[0,0],[-18.706,15.365],[0,0]],"v":[[-67,178],[-144,187.5],[-140,72.5],[-225.5,-3.5],[-79.518,-90.465],[11.59,-162.564],[97.554,-81.366],[226.5,3.5],[143.5,87],[165.5,180],[82,176.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":143,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-26.152,78.596],[0,0],[-49.255,5.092],[0,0],[-11.096,-27.533],[0,0],[11.338,-15.161],[0,0]],"o":[[0,0],[-17,-14],[0,0],[13.353,-39.632],[0,0],[43.855,-3.783],[0,0],[20.664,87.082],[0,0],[-14.425,19.026],[0,0]],"v":[[-67,178],[-145.997,186.169],[-137.005,64.18],[-216.848,-46.096],[-76.05,-97.557],[6.342,-167.614],[93.106,-90.977],[224.836,-47.082],[148.824,73.356],[172.156,173.677],[81.334,174.836]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":145,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-17,28],[0,0],[-49.179,5.975],[0,0],[-17.175,-23.781],[0,0],[10,-17],[0,0]],"o":[[0,0],[-17,-14],[0,0],[14.517,-23.911],[0,0],[48.213,-5.858],[0,0],[19.5,27],[0,0],[-12.274,20.865],[0,0]],"v":[[-67,178],[-147,185.5],[-135.5,60],[-212.5,-67.5],[-73.085,-99.798],[2.462,-172.78],[95.047,-87.383],[224,-72.5],[151.5,66.5],[175.5,170.5],[81,174]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.167,"y":0.167},"t":148,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-11.844,41.562],[0,0],[-49.158,6.145],[0,0],[-16.164,-70.086],[0,0],[12.662,-13.339],[0,0]],"o":[[0,0],[-17,-14],[0,0],[17.156,-68.439],[0,0],[40,-5],[0,0],[7.493,29.682],[0,0],[-16.555,17.205],[0,0]],"v":[[-67,178],[-145.003,186.831],[-137.164,65.657],[-219.156,-30.561],[-76.554,-96.959],[6.315,-178.136],[96.379,-83.927],[225.664,-23.914],[146.176,75.818],[168.844,176.823],[81.666,175.664]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.41,"y":0},"t":150,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.158,6.145],[0,0],[-2,-42.5],[0,0],[14,-11.5],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[40,-5],[0,0],[1.46,31.03],[0,0],[-18.706,15.365],[0,0]],"v":[[-67,178],[-144,187.5],[-138,68.5],[-222.5,-12],[-80.5,-70],[8.5,-194.5],[99.5,-63.5],[226.5,0.5],[143.5,80.5],[165.5,180],[82,176.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":154,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.158,6.145],[0,0],[-2,-42.5],[0,0],[7.39,-22.687],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[40,-5],[0,0],[1.46,31.03],[0,0],[-7.498,23.017],[0,0]],"v":[[-67,178],[-167.273,169.551],[-138,68.5],[-216.558,-37.642],[-80.5,-70],[8.5,-194.5],[99.5,-63.5],[225.014,-20.014],[143.5,80.5],[180.85,166.153],[82,176.5]],"c":false}]},{"i":{"x":0.61,"y":1},"o":{"x":0.167,"y":0.167},"t":157,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.158,6.145],[0,0],[-2,-42.5],[0,0],[7.39,-22.687],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[40,-5],[0,0],[1.46,31.03],[0,0],[-7.498,23.017],[0,0]],"v":[[-67,178],[-167.273,169.551],[-138,68.5],[-216.558,-37.642],[-80.5,-70],[8.5,-194.5],[99.5,-63.5],[225.014,-20.014],[143.5,80.5],[180.85,166.153],[82,176.5]],"c":false}]},{"i":{"x":0.61,"y":1},"o":{"x":0.41,"y":0},"t":163,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-52.5,1],[0,0],[-2,-42.5],[0,0],[14,-11.5],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[49.615,-0.945],[0,0],[1.46,31.03],[0,0],[-18.706,15.365],[0,0]],"v":[[-67,178],[-144.5,198.5],[-139,75],[-224.5,16.5],[-79.5,-66],[9,-188],[101,-58.5],[227,24.5],[143.5,84.5],[155.5,195],[82,176.5]],"c":false}]},{"i":{"x":0.59,"y":1},"o":{"x":0.41,"y":0},"t":173,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.158,6.145],[0,0],[-2,-42.5],[0,0],[14,-11.5],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[40,-5],[0,0],[1.46,31.03],[0,0],[-18.706,15.365],[0,0]],"v":[[-67,178],[-150,182],[-137.5,63.5],[-223.5,-26.5],[-80.5,-75],[9,-197],[99,-66.5],[227,-11.5],[143.5,80.5],[165.5,180],[82,176.5]],"c":false}]},{"t":179,"s":[{"i":[[0,0],[19.718,16.239],[0,0],[-7.512,35.211],[0,0],[-49.158,6.145],[0,0],[-2,-42.5],[0,0],[14,-11.5],[0,0]],"o":[[0,0],[-17,-14],[0,0],[8,-37.5],[0,0],[40,-5],[0,0],[1.46,31.03],[0,0],[-18.706,15.365],[0,0]],"v":[[-67,178],[-144,187.5],[-138,68.5],[-222.5,-12],[-80.5,-70],[8.5,-194.5],[99.5,-63.5],[226.5,0.5],[143.5,80.5],[165.5,180],[82,176.5]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.662745098039,0.282352941176,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.898039275525,0.400000029919,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":"STAR 2","sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.45],"y":[0]},"t":100,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":116,"s":[-2]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":123,"s":[1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":130,"s":[-2]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":137,"s":[1]},{"i":{"x":[0.833],"y":[0.583]},"o":{"x":[0.167],"y":[0.167]},"t":148,"s":[-2]},{"t":158,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.57,"y":1},"o":{"x":0.42,"y":0},"t":0,"s":[265.75,506.5,0],"to":[0,-1.917,0],"ti":[0,0,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.42,"y":0},"t":60,"s":[265.75,495,0],"to":[0,0,0],"ti":[0,-1.917,0]},{"i":{"x":0.57,"y":0.57},"o":{"x":0.42,"y":0.42},"t":69,"s":[265.75,506.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.42,"y":0},"t":90,"s":[265.75,506.5,0],"to":[0,-1.917,0],"ti":[0,0,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.42,"y":0},"t":150,"s":[265.75,495,0],"to":[0,0,0],"ti":[0,-1.917,0]},{"t":159,"s":[265.75,506.5,0]}]},"a":{"a":0,"k":[9.75,250.5,0]},"s":{"a":0,"k":[96,96,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":0,"s":[{"i":[[0,0],[6.625,7.806],[4,-11.5],[0,0]],"o":[[0,0],[-4.875,11.306],[14.5,6],[0,0]],"v":[[-52,203.5],[-61.125,177.194],[-76,226],[-53.5,234.5]],"c":true}]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":60,"s":[{"i":[[0,0],[6.625,7.806],[4,-11.5],[0,0]],"o":[[0,0],[-4.875,11.306],[14.5,6],[0,0]],"v":[[-53.5,194],[-61.625,156.194],[-76,226],[-53.5,237]],"c":true}]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":69,"s":[{"i":[[0,0],[12.625,3.806],[4,-11.5],[0,0]],"o":[[0,0],[-4.875,11.306],[14.5,6],[0,0]],"v":[[-52,203.5],[-65.625,191.194],[-76,226],[-53.5,234.5]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.43,"y":0},"t":79,"s":[{"i":[[0,0],[6.625,7.806],[4,-11.5],[0,0]],"o":[[0,0],[-4.875,11.306],[14.5,6],[0,0]],"v":[[-52,203.5],[-61.125,177.194],[-76,226],[-53.5,234.5]],"c":true}]},{"i":{"x":0.58,"y":1},"o":{"x":0.167,"y":0},"t":89,"s":[{"i":[[0,0],[6.625,7.806],[4,-11.5],[0,0]],"o":[[0,0],[-4.875,11.306],[14.5,6],[0,0]],"v":[[-52,203.5],[-61.125,177.194],[-76,226],[-53.5,234.5]],"c":true}]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":150,"s":[{"i":[[0,0],[6.625,7.806],[4,-11.5],[0,0]],"o":[[0,0],[-4.875,11.306],[14.5,6],[0,0]],"v":[[-53.5,194],[-61.625,156.194],[-76,226],[-53.5,237]],"c":true}]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":159,"s":[{"i":[[0,0],[12.625,3.806],[4,-11.5],[0,0]],"o":[[0,0],[-4.875,11.306],[14.5,6],[0,0]],"v":[[-52,203.5],[-65.625,191.194],[-76,226],[-53.5,234.5]],"c":true}]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":169,"s":[{"i":[[0,0],[6.625,7.806],[4,-11.5],[0,0]],"o":[[0,0],[-4.875,11.306],[14.5,6],[0,0]],"v":[[-52,203.5],[-61.125,177.194],[-76,226],[-53.5,234.5]],"c":true}]},{"t":179,"s":[{"i":[[0,0],[6.625,7.806],[4,-11.5],[0,0]],"o":[[0,0],[-4.875,11.306],[14.5,6],[0,0]],"v":[[-52,203.5],[-61.125,177.194],[-76,226],[-53.5,234.5]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.090196078431,0.321568627451,0.298039215686,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.960784373564,0.694117647059,0.152941176471,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 8","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":0,"s":[{"i":[[11.214,33.195],[5.212,-32.39]],"o":[[-9.308,-27.55],[-4.673,29.041]],"v":[[33.786,203.886],[-16.103,206.864]],"c":true}]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":60,"s":[{"i":[[8.332,34.033],[6.103,-42.864]],"o":[[-8.786,-35.886],[-4.146,29.121]],"v":[[33.786,198.386],[-15.103,198.864]],"c":true}]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":69,"s":[{"i":[[10.376,33.466],[1.603,-27.364]],"o":[[-6.786,-21.886],[-1.72,29.364]],"v":[[34.786,206.386],[-17.103,210.364]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.43,"y":0},"t":79,"s":[{"i":[[11.214,33.195],[5.212,-32.39]],"o":[[-9.308,-27.55],[-4.673,29.041]],"v":[[33.786,203.886],[-16.103,206.864]],"c":true}]},{"i":{"x":0.58,"y":1},"o":{"x":0.167,"y":0},"t":89,"s":[{"i":[[11.214,33.195],[5.212,-32.39]],"o":[[-9.308,-27.55],[-4.673,29.041]],"v":[[33.786,203.886],[-16.103,206.864]],"c":true}]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":150,"s":[{"i":[[8.332,34.033],[6.103,-42.864]],"o":[[-8.786,-35.886],[-4.146,29.121]],"v":[[33.786,198.386],[-15.103,198.864]],"c":true}]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":159,"s":[{"i":[[10.376,33.466],[1.603,-27.364]],"o":[[-6.786,-21.886],[-1.72,29.364]],"v":[[34.786,206.386],[-17.103,210.364]],"c":true}]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":169,"s":[{"i":[[11.214,33.195],[5.212,-32.39]],"o":[[-9.308,-27.55],[-4.673,29.041]],"v":[[33.786,203.886],[-16.103,206.864]],"c":true}]},{"t":179,"s":[{"i":[[11.214,33.195],[5.212,-32.39]],"o":[[-9.308,-27.55],[-4.673,29.041]],"v":[[33.786,203.886],[-16.103,206.864]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.090196078431,0.321568627451,0.298039215686,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.996078491211,0.956862804936,0.815686334348,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 7","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[-52.5,9],[0,0],[0,0]],"o":[[0,0],[0,0],[40.906,-7.012],[0,0],[0,0]],"v":[[-80.5,223.5],[-67,178],[5,84],[82,176.5],[100,222]],"c":false}]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":60,"s":[{"i":[[0,0],[0,0],[-52.5,9],[0,0],[0,0]],"o":[[0,0],[0,0],[40.906,-7.012],[0,0],[0,0]],"v":[[-80.5,223.5],[-66,146],[5,84],[82,147],[100,222]],"c":false}]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":69,"s":[{"i":[[0,0],[0,0],[-52.5,9],[0,0],[0,0]],"o":[[0,0],[0,0],[40.906,-7.012],[0,0],[0,0]],"v":[[-80.5,223.5],[-69,185.5],[5,84],[84,180],[100,222]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.43,"y":0},"t":79,"s":[{"i":[[0,0],[0,0],[-52.5,9],[0,0],[0,0]],"o":[[0,0],[0,0],[40.906,-7.012],[0,0],[0,0]],"v":[[-80.5,223.5],[-67,178],[5,84],[82,176.5],[100,222]],"c":false}]},{"i":{"x":0.58,"y":1},"o":{"x":0.167,"y":0},"t":89,"s":[{"i":[[0,0],[0,0],[-52.5,9],[0,0],[0,0]],"o":[[0,0],[0,0],[40.906,-7.012],[0,0],[0,0]],"v":[[-80.5,223.5],[-67,178],[5,84],[82,176.5],[100,222]],"c":false}]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[-52.5,9],[0,0],[0,0]],"o":[[0,0],[0,0],[40.906,-7.012],[0,0],[0,0]],"v":[[-80.5,223.5],[-66,146],[5,84],[82,147],[100,222]],"c":false}]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":159,"s":[{"i":[[0,0],[0,0],[-52.5,9],[0,0],[0,0]],"o":[[0,0],[0,0],[40.906,-7.012],[0,0],[0,0]],"v":[[-80.5,223.5],[-69,185.5],[5,84],[84,180],[100,222]],"c":false}]},{"i":{"x":0.58,"y":1},"o":{"x":0.43,"y":0},"t":169,"s":[{"i":[[0,0],[0,0],[-52.5,9],[0,0],[0,0]],"o":[[0,0],[0,0],[40.906,-7.012],[0,0],[0,0]],"v":[[-80.5,223.5],[-67,178],[5,84],[82,176.5],[100,222]],"c":false}]},{"t":179,"s":[{"i":[[0,0],[0,0],[-52.5,9],[0,0],[0,0]],"o":[[0,0],[0,0],[40.906,-7.012],[0,0],[0,0]],"v":[[-80.5,223.5],[-67,178],[5,84],[82,176.5],[100,222]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.662745098039,0.282352941176,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.898039275525,0.400000029919,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-56,0],[0,0]],"o":[[0,0],[0,0],[0,0],[56,0],[0,0]],"v":[[80,194],[-69,194.5],[-79.5,221],[12,250.5],[95.5,221.5]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.662745098039,0.282352941176,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.898039275525,0.400000029919,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 2","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0}]} \ No newline at end of file diff --git a/frontend/public/star-sing.json b/frontend/public/star-sing.json new file mode 100644 index 0000000..ebd5321 --- /dev/null +++ b/frontend/public/star-sing.json @@ -0,0 +1 @@ +{"tgs":1,"v":"5.5.2","fr":60,"ip":0,"op":140,"w":512,"h":512,"nm":"STAR_22","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 44","sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":0,"k":[233.2,286.013,0]},"a":{"a":0,"k":[50,50,0]},"s":{"a":0,"k":[90,90,100]}},"ao":0,"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"HAND02","parent":14,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.42],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[4]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.42],"y":[0]},"t":50,"s":[-128]},{"i":{"x":[0.26],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":65,"s":[-136]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.69],"y":[0]},"t":90,"s":[0]},{"i":{"x":[0.54],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":103,"s":[-23]},{"i":{"x":[0.54],"y":[1]},"o":{"x":[0.43],"y":[0]},"t":127,"s":[0]},{"t":140,"s":[0]}]},"p":{"a":0,"k":[-235.482,83.112,0]},"a":{"a":0,"k":[-235.482,83.112,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.42,"y":0},"t":0,"s":[{"i":[[0.47,-4.074],[-15.053,-12.579],[-3.152,-2.41],[12.5,10.25],[14.965,6.179]],"o":[[-0.944,8.18],[8.87,7.412],[23,10.75],[-5.391,-4.421],[-19.731,-8.147]],"v":[[-301.25,39],[-269.872,76.056],[-249.75,92],[-225.75,67.25],[-260.739,49.454]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[0.498,-4.318],[-14.623,-12.361],[-3.351,-2.563],[12.5,10.25],[4.69,1.266]],"o":[[-0.909,7.874],[9.518,8.046],[23,10.75],[-4.965,-4.071],[-6.961,-1.88]],"v":[[-301.25,39],[-271.567,74.631],[-249.75,92],[-225.75,67.25],[-257.199,50.938]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.42,"y":0},"t":50,"s":[{"i":[[13.378,2.355],[-8.156,-8.892],[-17.931,-3.037],[11.278,11.581],[4.331,2.2]],"o":[[-9.885,-1.74],[6.808,7.422],[18.6,1.94],[-4.48,-4.6],[-4.841,-2.458]],"v":[[-283.677,46.109],[-303.818,92.534],[-267.062,104.945],[-247.106,74.312],[-280.573,75.716]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[6.924,-0.86],[-11.605,-10.736],[-10.541,-2.724],[11.889,10.915],[9.648,4.189]],"o":[[-5.414,3.22],[7.839,7.417],[20.8,6.345],[-4.936,-4.51],[-12.286,-5.303]],"v":[[-292.464,42.554],[-286.845,84.295],[-258.406,98.473],[-236.428,70.781],[-270.656,62.585]],"c":true}]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[0.47,-4.074],[-15.053,-12.579],[-3.152,-2.41],[12.5,10.25],[14.965,6.179]],"o":[[-0.944,8.18],[8.87,7.412],[23,10.75],[-5.391,-4.421],[-19.731,-8.147]],"v":[[-301.25,39],[-269.872,76.056],[-249.75,92],[-225.75,67.25],[-260.739,49.454]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":90,"s":[{"i":[[0.47,-4.074],[-16.352,-14.036],[-3.152,-2.41],[12.515,10.231],[14.965,6.179]],"o":[[-0.944,8.18],[11.844,10.167],[23,10.75],[-4.351,-3.557],[-19.731,-8.147]],"v":[[-315.572,37.232],[-278.047,71.015],[-249.75,92],[-225.75,67.25],[-263.534,51.309]],"c":true}]},{"i":{"x":0.54,"y":1},"o":{"x":0.167,"y":0.167},"t":103,"s":[{"i":[[0.47,-4.074],[-15.053,-12.579],[-7.836,-1.32],[12.5,10.25],[6.109,1.957]],"o":[[-0.944,8.18],[8.87,7.412],[18.612,4.905],[-5.391,-4.421],[-7.788,-2.495]],"v":[[-294.279,42.956],[-272.48,83.685],[-246.385,93.085],[-225.75,67.25],[-255.477,61.478]],"c":true}]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0},"t":127,"s":[{"i":[[1.294,-3.891],[-12.159,-15.395],[-2.591,-3.004],[10.137,12.592],[13.383,9.112]],"o":[[-2.598,7.814],[7.165,9.071],[20.312,15.231],[-4.372,-5.431],[-17.646,-12.014]],"v":[[-294.707,26.751],[-271.58,69.445],[-255.148,89.171],[-226.59,69.859],[-257.195,45.277]],"c":true}]},{"t":140,"s":[{"i":[[0.47,-4.074],[-15.053,-12.579],[-3.152,-2.41],[12.5,10.25],[14.965,6.179]],"o":[[-0.944,8.18],[8.87,7.412],[23,10.75],[-5.391,-4.421],[-19.731,-8.147]],"v":[[-301.25,39],[-269.872,76.056],[-249.75,92],[-225.75,67.25],[-260.739,49.454]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.662745098039,0.282352941176,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.898039275525,0.400000029919,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"LIPS","parent":18,"sr":1,"ks":{"r":{"a":0,"k":-37},"p":{"a":0,"k":[-195.929,38.343,0]},"a":{"a":0,"k":[-216.286,15.538,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[8.006,-0.114],[-19.5,5.25],[-6.5,-10]],"o":[[-8.972,0.128],[-3.25,-19.5],[5.73,8.816]],"v":[[-218.778,33.622],[-220.75,13.25],[-201.5,4.25]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.662745098039,0.003921568627,0.003921568627,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.898039275525,0.400000029919,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[11.52,10.383],[-16.424,-8.986],[-5.923,10.352]],"o":[[-11.52,-10.383],[16.424,8.986],[5.923,-10.352]],"v":[[-195.332,8.16],[-216.504,34.782],[-189.037,31.346]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.662745098039,0.003921568627,0.003921568627,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.898039275525,0.400000029919,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 2","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"SAX 3","parent":8,"sr":1,"ks":{"p":{"a":0,"k":[-262.496,-44.339,0]},"a":{"a":0,"k":[-262.496,-44.339,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-348.774,-70.184],[-341.822,7.886],[-330.551,-0.053],[-341.443,-71.089]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.20000001496,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.89019613827,0.576470588235,0.145098039216,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 10","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-347.397,5.403],[-354.218,-69.538]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.20000001496,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529471603,0.352941176471,0.050980395897,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 9","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":0,"s":[{"i":[[8.545,8.273],[0,0],[63.387,-68.19],[9.178,4.11],[2.956,-3.818],[-63.628,64.481]],"o":[[-2.563,2.406],[0,0],[-27.635,29.73],[-5.401,2.662],[7.12,5.055],[53.695,-54.414]],"v":[[-357.559,-119.145],[-364.664,-111.677],[-413.452,-39.488],[-490.592,15.612],[-500.405,23.55],[-404.874,-34.434]],"c":true}]},{"i":{"x":0.44,"y":1},"o":{"x":0.167,"y":0.167},"t":9,"s":[{"i":[[14.598,9.418],[0,0],[63.387,-68.19],[9.178,4.11],[2.956,-3.818],[-63.628,64.481]],"o":[[-2.563,2.406],[0,0],[-27.635,29.73],[-5.401,2.662],[7.12,5.055],[53.695,-54.414]],"v":[[-351.127,-108.693],[-359.95,-100.627],[-402.894,-24.908],[-480.034,30.192],[-489.847,38.13],[-394.316,-19.854]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":20,"s":[{"i":[[8.545,8.273],[0,0],[63.387,-68.19],[9.178,4.11],[2.956,-3.818],[-63.628,64.481]],"o":[[-2.563,2.406],[0,0],[-27.635,29.73],[-5.401,2.662],[7.12,5.055],[53.695,-54.414]],"v":[[-357.559,-119.145],[-364.664,-111.677],[-413.452,-39.488],[-490.592,15.612],[-500.405,23.55],[-404.874,-34.434]],"c":true}]},{"i":{"x":0.43,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[8.545,8.273],[0,0],[63.387,-68.19],[9.178,4.11],[2.956,-3.818],[-63.628,64.481]],"o":[[-2.563,2.406],[0,0],[-27.635,29.73],[-5.401,2.662],[7.12,5.055],[53.695,-54.414]],"v":[[-357.559,-119.145],[-364.664,-111.677],[-413.452,-39.488],[-490.592,15.612],[-500.405,23.55],[-404.874,-34.434]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":90,"s":[{"i":[[9.286,7.433],[0,0],[47.708,-65.99],[9.524,3.229],[2.584,-4.079],[-50.306,59.056]],"o":[[-2.325,2.637],[0,0],[-23.781,32.894],[-5.126,3.158],[7.564,4.362],[43.215,-50.732]],"v":[[-394.89,-153.595],[-402.864,-145.532],[-438.893,-72.743],[-511.461,-17.953],[-519.3,-9.763],[-433.56,-68.019]],"c":true}]},{"i":{"x":0.43,"y":1},"o":{"x":0.167,"y":0.167},"t":98,"s":[{"i":[[8.545,8.273],[0,0],[63.387,-68.19],[9.178,4.11],[2.956,-3.818],[-63.628,64.481]],"o":[[-2.563,2.406],[0,0],[-27.635,29.73],[-5.401,2.662],[7.12,5.055],[53.695,-54.414]],"v":[[-357.559,-119.145],[-364.664,-111.677],[-413.452,-39.488],[-490.592,15.612],[-500.405,23.55],[-404.874,-34.434]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":127,"s":[{"i":[[9.286,7.433],[0,0],[47.708,-65.99],[9.524,3.229],[2.584,-4.079],[-50.306,59.056]],"o":[[-2.325,2.637],[0,0],[-23.781,32.894],[-5.126,3.158],[7.564,4.362],[43.215,-50.732]],"v":[[-394.89,-153.595],[-402.864,-145.532],[-438.893,-72.743],[-511.461,-17.953],[-519.3,-9.763],[-433.56,-68.019]],"c":true}]},{"i":{"x":0.44,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[14.598,9.418],[0,0],[63.387,-68.19],[9.178,4.11],[2.956,-3.818],[-63.628,64.481]],"o":[[-2.563,2.406],[0,0],[-27.635,29.73],[-5.401,2.662],[7.12,5.055],[53.695,-54.414]],"v":[[-351.127,-108.693],[-359.95,-100.627],[-402.894,-24.908],[-480.034,30.192],[-489.847,38.13],[-394.316,-19.854]],"c":true}]},{"t":140,"s":[{"i":[[8.545,8.273],[0,0],[63.387,-68.19],[9.178,4.11],[2.956,-3.818],[-63.628,64.481]],"o":[[-2.563,2.406],[0,0],[-27.635,29.73],[-5.401,2.662],[7.12,5.055],[53.695,-54.414]],"v":[[-357.559,-119.145],[-364.664,-111.677],[-413.452,-39.488],[-490.592,15.612],[-500.405,23.55],[-404.874,-34.434]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.20000001496,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.505882352941,0.20000001496,0.007843137255,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 5","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":0,"s":[{"i":[[-0.594,9.69],[-2.162,-6.375],[27.037,-27.328],[6.732,1.496],[-0.087,-2.572],[-12.299,10.738]],"o":[[0.594,-9.69],[1.89,5.573],[-19.805,20.019],[-2.141,4.203],[25.341,2.857],[21.425,-18.707]],"v":[[-359.473,-106.921],[-378.033,-98.978],[-421.172,-43.599],[-478.725,0.898],[-493.462,19.087],[-408.092,-38.334]],"c":true}]},{"i":{"x":0.44,"y":1},"o":{"x":0.167,"y":0.167},"t":9,"s":[{"i":[[-0.482,9.696],[-1.982,-16.188],[26.719,-27.639],[6.749,1.418],[-0.117,-2.571],[-12.174,10.88]],"o":[[0.482,-9.696],[0.955,7.799],[-19.572,20.247],[-2.092,4.228],[25.372,2.563],[21.208,-18.954]],"v":[[-350.373,-95.934],[-368.673,-86.67],[-412.623,-30.691],[-469.656,14.47],[-484.182,32.827],[-399.483,-25.577]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":20,"s":[{"i":[[-0.594,9.69],[-2.162,-6.375],[27.037,-27.328],[6.732,1.496],[-0.087,-2.572],[-12.299,10.738]],"o":[[0.594,-9.69],[1.89,5.573],[-19.805,20.019],[-2.141,4.203],[25.341,2.857],[21.425,-18.707]],"v":[[-359.473,-106.921],[-378.033,-98.978],[-421.172,-43.599],[-478.725,0.898],[-493.462,19.087],[-408.092,-38.334]],"c":true}]},{"i":{"x":0.43,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[-0.594,9.69],[-2.162,-6.375],[27.037,-27.328],[6.732,1.496],[-0.087,-2.572],[-12.299,10.738]],"o":[[0.594,-9.69],[1.89,5.573],[-19.805,20.019],[-2.141,4.203],[25.341,2.857],[21.425,-18.707]],"v":[[-359.473,-106.921],[-378.033,-98.978],[-421.172,-43.599],[-478.725,0.898],[-493.462,19.087],[-408.092,-38.334]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":90,"s":[{"i":[[0.539,9.693],[-0.235,-10.337],[13.68,-18.117],[6.861,0.702],[-0.386,-2.545],[-10.701,12.331]],"o":[[-0.539,-9.693],[0.27,11.903],[-16.969,22.473],[-1.637,4.424],[25.501,-0.115],[30.835,-35.534]],"v":[[-401.894,-141.08],[-421.574,-127.303],[-447.908,-82.673],[-494.997,-41.374],[-511.502,-18.405],[-438.9,-72.699]],"c":true}]},{"i":{"x":0.43,"y":1},"o":{"x":0.167,"y":0.167},"t":98,"s":[{"i":[[-0.594,9.69],[-2.162,-6.375],[27.037,-27.328],[6.732,1.496],[-0.087,-2.572],[-12.299,10.738]],"o":[[0.594,-9.69],[1.89,5.573],[-19.805,20.019],[-2.141,4.203],[25.341,2.857],[21.425,-18.707]],"v":[[-359.473,-106.921],[-378.033,-98.978],[-421.172,-43.599],[-478.725,0.898],[-493.462,19.087],[-408.092,-38.334]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":127,"s":[{"i":[[0.539,9.693],[-0.235,-10.337],[13.68,-18.117],[6.861,0.702],[-0.386,-2.545],[-10.701,12.331]],"o":[[-0.539,-9.693],[0.27,11.903],[-16.969,22.473],[-1.637,4.424],[25.501,-0.115],[30.835,-35.534]],"v":[[-401.894,-141.08],[-421.574,-127.303],[-447.908,-82.673],[-494.997,-41.374],[-511.502,-18.405],[-438.9,-72.699]],"c":true}]},{"i":{"x":0.44,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[-0.482,9.696],[-1.982,-16.188],[26.719,-27.639],[6.749,1.418],[-0.117,-2.571],[-12.174,10.88]],"o":[[0.482,-9.696],[0.955,7.799],[-19.572,20.247],[-2.092,4.228],[25.372,2.563],[21.208,-18.954]],"v":[[-350.373,-95.934],[-368.673,-86.67],[-412.623,-30.691],[-469.656,14.47],[-484.182,32.827],[-399.483,-25.577]],"c":true}]},{"t":140,"s":[{"i":[[-0.594,9.69],[-2.162,-6.375],[27.037,-27.328],[6.732,1.496],[-0.087,-2.572],[-12.299,10.738]],"o":[[0.594,-9.69],[1.89,5.573],[-19.805,20.019],[-2.141,4.203],[25.341,2.857],[21.425,-18.707]],"v":[[-359.473,-106.921],[-378.033,-98.978],[-421.172,-43.599],[-478.725,0.898],[-493.462,19.087],[-408.092,-38.334]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.20000001496,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.968627510819,0.686274509804,0.298039215686,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 6","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":0,"s":[{"i":[[0,0],[22.391,-21.509],[0,0],[0,0],[0,0]],"o":[[0,0],[30.859,-3.082],[0,0],[0,0],[0,0]],"v":[[-386.81,-67.361],[-444.252,-11.067],[-370.077,1.778],[-344.753,-26.683],[-366.164,-47.392]],"c":true}]},{"i":{"x":0.44,"y":1},"o":{"x":0.167,"y":0.167},"t":9,"s":[{"i":[[0,0],[42.015,-33.446],[0,0],[0,0],[0,0]],"o":[[0,0],[30.859,-3.082],[0,0],[0,0],[0,0]],"v":[[-360.125,-68.093],[-430.076,9.527],[-370.077,1.778],[-344.753,-26.683],[-351.612,-42.344]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":20,"s":[{"i":[[0,0],[22.391,-21.509],[0,0],[0,0],[0,0]],"o":[[0,0],[30.859,-3.082],[0,0],[0,0],[0,0]],"v":[[-386.81,-67.361],[-444.252,-11.067],[-370.077,1.778],[-344.753,-26.683],[-366.164,-47.392]],"c":true}]},{"i":{"x":0.43,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,0],[22.391,-21.509],[0,0],[0,0],[0,0]],"o":[[0,0],[30.859,-3.082],[0,0],[0,0],[0,0]],"v":[[-386.81,-67.361],[-444.252,-11.067],[-370.077,1.778],[-344.753,-26.683],[-366.164,-47.392]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":90,"s":[{"i":[[0,0],[22.391,-21.509],[0,0],[0,0],[9.421,11.493]],"o":[[0,0],[35.704,-7.147],[0,0],[0,0],[-11.322,-13.812]],"v":[[-413.045,-99.491],[-464.338,-43.913],[-370.077,1.778],[-344.753,-26.683],[-374.835,-53.888]],"c":true}]},{"i":{"x":0.43,"y":1},"o":{"x":0.167,"y":0.167},"t":98,"s":[{"i":[[0,0],[22.391,-21.509],[0,0],[0,0],[0,0]],"o":[[0,0],[30.859,-3.082],[0,0],[0,0],[0,0]],"v":[[-386.81,-67.361],[-444.252,-11.067],[-370.077,1.778],[-344.753,-26.683],[-366.164,-47.392]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":127,"s":[{"i":[[0,0],[22.391,-21.509],[0,0],[0,0],[9.421,11.493]],"o":[[0,0],[35.704,-7.147],[0,0],[0,0],[-11.322,-13.812]],"v":[[-413.045,-99.491],[-464.338,-43.913],[-370.077,1.778],[-344.753,-26.683],[-374.835,-53.888]],"c":true}]},{"i":{"x":0.44,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[0,0],[42.015,-33.446],[0,0],[0,0],[0,0]],"o":[[0,0],[30.859,-3.082],[0,0],[0,0],[0,0]],"v":[[-360.125,-68.093],[-430.076,9.527],[-370.077,1.778],[-344.753,-26.683],[-351.612,-42.344]],"c":true}]},{"t":140,"s":[{"i":[[0,0],[22.391,-21.509],[0,0],[0,0],[0,0]],"o":[[0,0],[30.859,-3.082],[0,0],[0,0],[0,0]],"v":[[-386.81,-67.361],[-444.252,-11.067],[-370.077,1.778],[-344.753,-26.683],[-366.164,-47.392]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.323529411765,0.27827761781,0.052018453561,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529471603,0.352941176471,0.050980395897,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 8","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[0,0],[7.666,-6.309],[0,0],[-6.54,-6.669]],"o":[[0,0],[0,0],[0,0],[-14.207,11.692],[0,0],[-0.5,25.981]],"v":[[-342.435,-38.379],[-360.022,-57.921],[-378.299,-78.231],[-418.613,-31.96],[-468.378,2.162],[-406.845,44.241]],"c":false}]},{"i":{"x":0.44,"y":1},"o":{"x":0.167,"y":0.167},"t":9,"s":[{"i":[[0,0],[0,0],[0,0],[23.58,-23.432],[0,0],[-6.54,-6.669]],"o":[[0,0],[0,0],[0,0],[-20.619,20.489],[0,0],[-0.5,25.981]],"v":[[-342.435,-38.379],[-354.772,-64.363],[-355.989,-84.75],[-410.62,-20.16],[-465.652,22.555],[-406.845,44.241]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":20,"s":[{"i":[[0,0],[0,0],[0,0],[7.666,-6.309],[0,0],[-6.54,-6.669]],"o":[[0,0],[0,0],[0,0],[-14.207,11.692],[0,0],[-0.5,25.981]],"v":[[-342.435,-38.379],[-360.022,-57.921],[-378.299,-78.231],[-418.613,-31.96],[-468.378,2.162],[-406.845,44.241]],"c":false}]},{"i":{"x":0.43,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,0],[0,0],[0,0],[7.666,-6.309],[0,0],[-6.54,-6.669]],"o":[[0,0],[0,0],[0,0],[-14.207,11.692],[0,0],[-0.5,25.981]],"v":[[-342.435,-38.379],[-360.022,-57.921],[-378.299,-78.231],[-418.613,-31.96],[-468.378,2.162],[-406.845,44.241]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":90,"s":[{"i":[[0,0],[15.553,19.764],[0,0],[12.118,-14.09],[0,0],[-11.54,-40.252]],"o":[[0,0],[-13.468,-17.114],[0,0],[-11.998,13.95],[0,0],[-0.5,25.981]],"v":[[-342.435,-38.379],[-372.086,-70.141],[-412.43,-119.634],[-446.837,-70.665],[-486.516,-29.077],[-406.845,44.241]],"c":false}]},{"i":{"x":0.43,"y":1},"o":{"x":0.167,"y":0.167},"t":98,"s":[{"i":[[0,0],[0,0],[0,0],[7.666,-6.309],[0,0],[-6.54,-6.669]],"o":[[0,0],[0,0],[0,0],[-14.207,11.692],[0,0],[-0.5,25.981]],"v":[[-342.435,-38.379],[-360.022,-57.921],[-378.299,-78.231],[-418.613,-31.96],[-468.378,2.162],[-406.845,44.241]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":127,"s":[{"i":[[0,0],[15.553,19.764],[0,0],[12.118,-14.09],[0,0],[-11.54,-40.252]],"o":[[0,0],[-13.468,-17.114],[0,0],[-11.998,13.95],[0,0],[-0.5,25.981]],"v":[[-342.435,-38.379],[-372.086,-70.141],[-412.43,-119.634],[-446.837,-70.665],[-486.516,-29.077],[-406.845,44.241]],"c":false}]},{"i":{"x":0.44,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[0,0],[0,0],[0,0],[23.58,-23.432],[0,0],[-6.54,-6.669]],"o":[[0,0],[0,0],[0,0],[-20.619,20.489],[0,0],[-0.5,25.981]],"v":[[-342.435,-38.379],[-354.772,-64.363],[-355.989,-84.75],[-410.62,-20.16],[-465.652,22.555],[-406.845,44.241]],"c":false}]},{"t":140,"s":[{"i":[[0,0],[0,0],[0,0],[7.666,-6.309],[0,0],[-6.54,-6.669]],"o":[[0,0],[0,0],[0,0],[-14.207,11.692],[0,0],[-0.5,25.981]],"v":[[-342.435,-38.379],[-360.022,-57.921],[-378.299,-78.231],[-418.613,-31.96],[-468.378,2.162],[-406.845,44.241]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.20000001496,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.89019613827,0.576470588235,0.145098039216,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-0.129,-0.214]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":25,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":106,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":107,"s":[100]},{"t":140,"s":[100]}]},"r":{"a":1,"k":[{"i":{"x":[0.639],"y":[0.573]},"o":{"x":[0.303],"y":[0.255]},"t":0,"s":[23.646]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.306],"y":[-0.573]},"t":25,"s":[12.164]},{"i":{"x":[0.567],"y":[0.554]},"o":{"x":[0.222],"y":[0.316]},"t":107,"s":[36]},{"t":140,"s":[23.646]}]},"p":{"a":1,"k":[{"i":{"x":0.659,"y":0.508},"o":{"x":0.313,"y":0.525},"t":0,"s":[131.03,42.694,0],"to":[-4.104,0.885,0],"ti":[3.276,-1.605,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.311,"y":0.098},"t":25,"s":[119.731,46.825,0],"to":[-3.482,1.706,0],"ti":[1.417,15.583,0]},{"i":{"x":0.629,"y":0.821},"o":{"x":0.167,"y":0.167},"t":107,"s":[149.06,224.761,0],"to":[-0.156,-1.713,0],"ti":[2.702,15.596,0]},{"i":{"x":0.616,"y":0.869},"o":{"x":0.27,"y":0.611},"t":121,"s":[159.593,69.39,0],"to":[-4.472,-25.812,0],"ti":[11.342,-2.445,0]},{"t":140,"s":[131.03,42.694,0]}]},"a":{"a":0,"k":[-115.44,-124.739,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0.134,-17.141],[5.969,16.793],[6.445,2.659],[-13.578,-6.969],[0.598,13.239]],"o":[[-0.051,6.47],[-4.835,-13.602],[-6.445,-2.659],[12.125,6.223],[7.261,11.211]],"v":[[-101.805,-113.922],[-94.337,-131.962],[-116.225,-165.334],[-134.886,-135.349],[-113.433,-152.275]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.20000001496,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.096282211005,0.1580806059,0.621568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"note02","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":9,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":106,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":107,"s":[100]},{"t":140,"s":[100]}]},"r":{"a":1,"k":[{"i":{"x":[0.601],"y":[1]},"o":{"x":[0.284],"y":[0.66]},"t":0,"s":[3.683]},{"i":{"x":[0.835],"y":[0.873]},"o":{"x":[0.167],"y":[0]},"t":20,"s":[3]},{"i":{"x":[0.081],"y":[0.804]},"o":{"x":[0.227],"y":[0.149]},"t":107,"s":[16]},{"t":140,"s":[3.683]}]},"p":{"a":1,"k":[{"i":{"x":0.601,"y":1},"o":{"x":0.284,"y":0.661},"t":0,"s":[4.189,-191.206,0],"to":[-0.389,-0.593,0],"ti":[2.189,2.293,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":20,"s":[-1.46,-198.272,0],"to":[-9.269,-9.713,0],"ti":[0.931,9.807,0]},{"i":{"x":0.068,"y":0.806},"o":{"x":0.167,"y":0.167},"t":107,"s":[-38.127,-36.606,0],"to":[-3.785,-39.879,0],"ti":[30.631,46.636,0]},{"t":140,"s":[4.189,-191.206,0]}]},"a":{"a":0,"k":[-336.204,-176.426,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[1.214,1.342],[9.5,-3.75],[2.75,-3.5],[-4.75,-0.75],[-9.5,12.75],[-1.5,-17],[4.75,11.25],[10.25,-20.25],[-9.39,13.452]],"o":[[-2.792,-3.085],[-9.5,3.75],[-2.914,3.709],[4.75,0.75],[-3.5,7.5],[4.25,21.5],[-3.437,-8.14],[3.25,-21.75],[8.398,-12.031]],"v":[[-314.75,-228.5],[-346.75,-197.25],[-361,-196.5],[-354.5,-179.75],[-333.75,-196.5],[-349.25,-144.25],[-312.25,-160.5],[-340.75,-153.75],[-314.398,-214.969]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.096282211005,0.1580806059,0.621568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"note01","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":73,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":74,"s":[100]},{"t":140,"s":[100]}]},"r":{"a":1,"k":[{"i":{"x":[0.652],"y":[1]},"o":{"x":[0.318],"y":[0.645]},"t":0,"s":[-17.396]},{"i":{"x":[0.833],"y":[0.855]},"o":{"x":[0.167],"y":[0]},"t":7,"s":[-17]},{"i":{"x":[0.599],"y":[0.928]},"o":{"x":[0.333],"y":[0.027]},"t":74,"s":[0]},{"i":{"x":[0.423],"y":[0.826]},"o":{"x":[0.166],"y":[-0.274]},"t":85,"s":[-38.285]},{"t":140,"s":[-17.396]}]},"p":{"a":1,"k":[{"i":{"x":0.652,"y":1},"o":{"x":0.318,"y":0.659},"t":0,"s":[-155.632,-194.514,0],"to":[-0.04,0.008,0],"ti":[0.374,-0.067,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":7,"s":[-156.795,-194.295,0],"to":[-0.044,0.008,0],"ti":[-2.157,20.979,0]},{"i":{"x":0.599,"y":0.684},"o":{"x":0.167,"y":0.167},"t":74,"s":[-14.923,-85.89,0],"to":[3.324,-32.33,0],"ti":[14.71,16.588,0]},{"i":{"x":0.423,"y":0.924},"o":{"x":0.166,"y":0.48},"t":85,"s":[-15.576,-192.003,0],"to":[-32.667,-36.838,0],"ti":[26.277,-5.154,0]},{"t":140,"s":[-155.632,-194.514,0]}]},"a":{"a":0,"k":[-423.223,-176.877,0]},"s":{"a":1,"k":[{"i":{"x":[0.652,0.652,0.652],"y":[1,1,1]},"o":{"x":[0.318,0.318,0.318],"y":[0.654,0.654,0.142]},"t":0,"s":[99.902,99.902,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.859,0.859,3.557]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":7,"s":[100,100,100]},{"i":{"x":[0.26,0.26,0.26],"y":[0.922,0.922,-2.114]},"o":{"x":[0.177,0.177,0.177],"y":[0.173,0.173,6.901]},"t":74,"s":[81.917,81.917,100]},{"t":140,"s":[99.902,99.902,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.54,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[2.034,-1.09],[11.164,-4.618],[-2.558,-2.826],[0.5,-25.5],[15.438,9.924],[0,0],[4.5,13.25],[-10.356,7.027],[-1.25,-13.25],[13.5,7.25],[1.25,-5.5],[-1.739,27.432]],"o":[[-39.12,21.105],[-3.381,1.398],[6.301,6.962],[7.75,19.25],[-14,-9],[0,0],[18.423,-0.246],[-0.25,33.75],[15,21.75],[-13.047,-7.007],[-2.282,-17.343],[0.166,-2.621]],"v":[[-400.244,-206.073],[-469.293,-191.605],[-471.051,-181.212],[-457.5,-117],[-426.25,-145.75],[-449.544,-133.2],[-453.211,-165.129],[-401.04,-177.495],[-397.748,-125.001],[-368.248,-156.251],[-391.498,-139.001],[-387.617,-207.208]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":7,"s":[{"i":[[2.05,-1.07],[11.164,-4.618],[-2.558,-2.826],[0.5,-25.5],[15.438,9.924],[0,0],[4.5,13.25],[-10.542,7.013],[-1.25,-13.25],[13.5,7.25],[1.25,-5.5],[-1.739,27.432]],"o":[[-39.232,20.474],[-3.381,1.398],[6.301,6.962],[7.75,19.25],[-14,-9],[0,0],[18.122,0.138],[-0.25,33.75],[15,21.75],[-13.047,-7.007],[-2.282,-17.343],[0.166,-2.621]],"v":[[-400.222,-205.389],[-469.293,-191.605],[-471.051,-181.212],[-457.5,-117],[-426.25,-145.75],[-449.544,-133.2],[-453.211,-165.129],[-400.947,-176.988],[-397.653,-124.394],[-368.153,-155.644],[-391.403,-138.394],[-387.521,-206.601]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":74,"s":[{"i":[[2.304,-0.191],[11.164,-4.618],[-2.558,-2.826],[0.5,-25.5],[15.438,9.924],[0,0],[4.5,13.25],[-4,7.5],[-1.25,-13.25],[13.5,7.25],[1.25,-5.5],[-1.739,27.432]],"o":[[-33.439,2.773],[-3.381,1.398],[6.301,6.962],[7.75,19.25],[-14,-9],[0,0],[33.691,0.85],[-0.25,33.75],[15,21.75],[-13.047,-7.007],[-2.282,-17.343],[0.166,-2.621]],"v":[[-396.253,-192.145],[-469.293,-191.605],[-471.051,-181.212],[-457.5,-117],[-426.25,-145.75],[-449.544,-133.2],[-453.211,-165.129],[-399.459,-157.531],[-396.253,-108.449],[-366.753,-139.699],[-390.003,-122.449],[-386.121,-190.656]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[1.474,-1.781],[11.164,-4.618],[-2.558,-2.826],[0.5,-25.5],[15.438,9.924],[0,0],[4.5,13.25],[-4,7.5],[-1.25,-13.25],[13.5,7.25],[1.25,-5.5],[-1.739,27.432]],"o":[[-35.302,42.646],[-3.381,1.398],[6.301,6.962],[7.75,19.25],[-14,-9],[0,0],[28.711,-13.382],[-0.25,33.75],[15,21.75],[-13.047,-7.007],[-2.282,-17.343],[0.166,-2.621]],"v":[[-401,-229.446],[-469.293,-191.605],[-471.051,-181.212],[-457.5,-117],[-426.25,-145.75],[-449.544,-133.2],[-453.211,-165.129],[-404.206,-194.832],[-401,-145.75],[-371.5,-177],[-394.75,-159.75],[-390.868,-227.957]],"c":true}]},{"t":140,"s":[{"i":[[2.034,-1.09],[11.164,-4.618],[-2.558,-2.826],[0.5,-25.5],[15.438,9.924],[0,0],[4.5,13.25],[-10.356,7.027],[-1.25,-13.25],[13.5,7.25],[1.25,-5.5],[-1.739,27.432]],"o":[[-39.12,21.105],[-3.381,1.398],[6.301,6.962],[7.75,19.25],[-14,-9],[0,0],[18.423,-0.246],[-0.25,33.75],[15,21.75],[-13.047,-7.007],[-2.282,-17.343],[0.166,-2.621]],"v":[[-400.244,-206.073],[-469.293,-191.605],[-471.051,-181.212],[-457.5,-117],[-426.25,-145.75],[-449.544,-133.2],[-453.211,-165.129],[-401.04,-177.495],[-397.748,-125.001],[-368.248,-156.251],[-391.498,-139.001],[-387.617,-207.208]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.096282211005,0.1580806059,0.621568627451,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"SAX","parent":1,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.42],"y":[0]},"t":0,"s":[31]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[25]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.42],"y":[0]},"t":50,"s":[-93]},{"i":{"x":[0.26],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":65,"s":[-58]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.69],"y":[0]},"t":90,"s":[50]},{"i":{"x":[0.26],"y":[1]},"o":{"x":[0.169],"y":[0.047]},"t":103,"s":[18]},{"i":{"x":[0.54],"y":[1]},"o":{"x":[0.69],"y":[0]},"t":127,"s":[50]},{"t":140,"s":[31]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.42,"y":0},"t":0,"s":[108.443,20.327,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.487,"y":0.681},"o":{"x":0.167,"y":0.167},"t":15,"s":[89.443,46.327,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.472,"y":1},"o":{"x":0.183,"y":0.534},"t":25,"s":[31.784,29.439,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.42,"y":0},"t":50,"s":[9.443,52.327,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[44.685,42.911,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.42,"y":0},"t":77,"s":[114.943,15.827,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":90,"s":[112.443,16.827,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.167,"y":0.167},"t":103,"s":[105.943,21.327,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.57,"y":1},"o":{"x":0.42,"y":0},"t":127,"s":[117.943,14.327,0],"to":[0,0,0],"ti":[0,0,0]},{"t":140,"s":[108.443,20.327,0]}]},"a":{"a":0,"k":[-262.496,-44.339,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-392.595,156.896],[-393.479,140.819]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.850980451995,0.670588235294,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 11","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-392.854,123.337],[-392.197,52.143]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.852733537263,0.670588235294,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 10","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[0,0],[1.442,-13.935],[0,0],[0,0],[0.906,-13.353],[0,0],[0,0],[0.544,-15.216],[0,0]],"o":[[0,0],[-1.226,11.845],[0,0],[0,0],[-1.06,15.62],[0,0],[0,0],[-0.573,16.025],[0,0]],"v":[[-408.443,62.546],[-424.424,73.944],[-410.624,91.169],[-411.42,99.035],[-426.019,111.383],[-411.08,129.121],[-410.321,136.699],[-425.093,150.286],[-409.082,166.861]],"c":true}]},{"i":{"x":0.52,"y":1},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,0],[1.442,-13.935],[0,0],[0,0],[0.906,-13.353],[0,0],[0,0],[0.544,-15.216],[0,0]],"o":[[0,0],[-1.226,11.845],[0,0],[0,0],[-1.06,15.62],[0,0],[0,0],[-0.573,16.025],[0,0]],"v":[[-408.443,62.546],[-424.424,73.944],[-416.649,88.014],[-418.394,98.246],[-438.777,103.415],[-424.878,124.011],[-426.634,134.172],[-457.296,140.225],[-426.562,165.134]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.49,"y":0},"t":31,"s":[{"i":[[0,0],[1.442,-13.935],[0,0],[0,0],[0.906,-13.353],[0,0],[0,0],[0.544,-15.216],[0,0]],"o":[[0,0],[-1.226,11.845],[0,0],[0,0],[-1.06,15.62],[0,0],[0,0],[-0.573,16.025],[0,0]],"v":[[-408.443,62.546],[-424.424,73.944],[-410.624,91.169],[-411.42,99.035],[-426.019,111.383],[-411.08,129.121],[-410.321,136.699],[-425.093,150.286],[-409.082,166.861]],"c":true}]},{"i":{"x":0.52,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,0],[1.442,-13.935],[0,0],[0,0],[0.906,-13.353],[0,0],[0,0],[0.544,-15.216],[0,0]],"o":[[0,0],[-1.226,11.845],[0,0],[0,0],[-1.06,15.62],[0,0],[0,0],[-0.573,16.025],[0,0]],"v":[[-408.443,62.546],[-424.424,73.944],[-410.624,91.169],[-411.42,99.035],[-426.019,111.383],[-411.08,129.121],[-410.321,136.699],[-425.093,150.286],[-409.082,166.861]],"c":true}]},{"t":80,"s":[{"i":[[0,0],[1.442,-13.935],[0,0],[0,0],[0.906,-13.353],[0,0],[0,0],[0.544,-15.216],[0,0]],"o":[[0,0],[-1.226,11.845],[0,0],[0,0],[-1.06,15.62],[0,0],[0,0],[-0.573,16.025],[0,0]],"v":[[-408.443,62.546],[-424.424,73.944],[-410.624,91.169],[-411.42,99.035],[-426.019,111.383],[-411.08,129.121],[-410.321,136.699],[-425.093,150.286],[-409.082,166.861]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.323529411765,0.27827761781,0.052018453561,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.505882352941,0.20000001496,0.007843137255,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 7","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-268.448,-51.632],[-266.781,-38.273]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.20000001496,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.305882352941,0.262745098039,0.050980395897,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 6","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-10.75,-2.915],[-6.131,0.732],[-0.062,5.701],[-6.406,-0.765],[9.241,4.32],[5.742,-4.6],[4.377,-2.418],[3.679,5.108],[0,0]],"o":[[0,0],[10.75,2.915],[8.574,-1.023],[0.094,-8.62],[6.892,1.619],[-14.382,-3.957],[-5.742,4.6],[-4.377,2.418],[-1.614,-2.241],[0,0]],"v":[[-336.919,-84.068],[-321.224,-7.056],[-282.194,-10.337],[-275.563,-31.422],[-255.714,-35.818],[-256.477,-51.676],[-289.863,-52.655],[-293.868,-33.02],[-308.482,-34.658],[-316.569,-84.456]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.20000001496,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.89019613827,0.576470588235,0.145098039216,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[0,0],[-1.343,-27.248],[18.578,-10.834],[-1.88,14.176],[0,0]],"o":[[0,0],[1.343,27.248],[27.502,-6.053],[1.88,-14.176],[0,0]],"v":[[-327.788,-74.26],[-305.437,143.022],[-337.969,200.978],[-295.5,150.671],[-318.909,-70.597]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,0],[-1.343,-27.248],[26.786,0.467],[-1.88,14.176],[0,0]],"o":[[0,0],[1.343,27.248],[39.761,10.001],[1.88,-14.176],[0,0]],"v":[[-327.788,-74.26],[-305.437,143.022],[-345.903,194.416],[-295.5,150.671],[-318.909,-70.597]],"c":true}]},{"i":{"x":0.52,"y":1},"o":{"x":0.167,"y":0.167},"t":22,"s":[{"i":[[0,0],[-1.343,-27.248],[26.786,0.467],[-1.88,14.176],[0,0]],"o":[[0,0],[1.343,27.248],[39.761,10.001],[1.88,-14.176],[0,0]],"v":[[-327.788,-74.26],[-305.437,143.022],[-345.897,192.18],[-295.5,150.671],[-318.909,-70.597]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":31,"s":[{"i":[[0,0],[-1.343,-27.248],[18.578,-10.834],[-1.88,14.176],[0,0]],"o":[[0,0],[1.343,27.248],[27.502,-6.053],[1.88,-14.176],[0,0]],"v":[[-327.788,-74.26],[-305.437,143.022],[-337.969,200.978],[-295.5,150.671],[-318.909,-70.597]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,0],[-1.343,-27.248],[18.578,-10.834],[-1.88,14.176],[0,0]],"o":[[0,0],[1.343,27.248],[27.502,-6.053],[1.88,-14.176],[0,0]],"v":[[-327.788,-74.26],[-305.437,143.022],[-337.969,200.978],[-295.5,150.671],[-318.909,-70.597]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68,"s":[{"i":[[0,0],[-1.343,-27.248],[18.578,-10.834],[-1.88,14.176],[0,0]],"o":[[0,0],[1.343,27.248],[34.076,-2.034],[1.88,-14.176],[0,0]],"v":[[-327.788,-74.26],[-305.437,143.022],[-332.493,199.296],[-293.195,152.875],[-318.909,-70.597]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[{"i":[[0,0],[-1.343,-27.248],[18.578,-10.834],[-1.88,14.176],[0,0]],"o":[[0,0],[1.343,27.248],[39.3,0.378],[1.88,-14.176],[0,0]],"v":[[-327.788,-74.26],[-305.437,143.022],[-330.966,203.844],[-291.779,154.228],[-318.909,-70.597]],"c":true}]},{"i":{"x":0.52,"y":1},"o":{"x":0.167,"y":0.167},"t":75,"s":[{"i":[[0,0],[-1.343,-27.248],[18.578,-10.834],[23.831,54.385],[0,0]],"o":[[0,0],[1.343,27.248],[39.3,0.378],[-1.75,-20.099],[0,0]],"v":[[-327.788,-74.26],[-305.437,143.022],[-330.966,203.844],[-290.791,161.122],[-318.909,-70.597]],"c":true}]},{"t":80,"s":[{"i":[[0,0],[-1.343,-27.248],[18.578,-10.834],[-1.88,14.176],[0,0]],"o":[[0,0],[1.343,27.248],[27.502,-6.053],[1.88,-14.176],[0,0]],"v":[[-327.788,-74.26],[-305.437,143.022],[-337.969,200.978],[-295.5,150.671],[-318.909,-70.597]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.323529411765,0.27827761781,0.052018453561,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529411765,0.351334575578,0.051672434339,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 9","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[-3.445,-22.685],[-26.96,2.898],[-3.747,23.205],[0.717,9.899],[-1.591,-25.112],[0,0]],"o":[[3.681,24.243],[30.907,-3.322],[3.747,-23.205],[-1.551,-21.421],[1.302,20.55],[0,0]],"v":[[-406.473,162.277],[-349.257,209.498],[-291.136,153.401],[-318.927,-103.718],[-356.793,-99.365],[-335.803,128.38]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[7.827,-21.569],[-39.628,-3.186],[-3.747,23.205],[0.717,9.899],[-1.591,-25.112],[0,0]],"o":[[-8.67,23.891],[39.9,3.208],[3.747,-23.205],[-1.551,-21.421],[1.302,20.55],[0,0]],"v":[[-426.376,162.642],[-346.044,200.433],[-291.136,153.401],[-318.927,-103.718],[-356.793,-99.365],[-335.803,128.38]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":22,"s":[{"i":[[7.326,-21.619],[-37.888,-9.954],[-3.747,23.205],[0.717,9.899],[-1.591,-25.112],[0,0]],"o":[[-8.121,23.906],[41.187,10.821],[3.747,-23.205],[-1.551,-21.421],[1.302,20.55],[0,0]],"v":[[-423.681,143.2],[-347.296,197.479],[-291.136,153.401],[-318.927,-103.718],[-356.793,-99.365],[-335.803,128.38]],"c":false}]},{"i":{"x":0.52,"y":1},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[6.825,-21.668],[-38.502,-2.646],[-3.747,23.205],[0.717,9.899],[-1.591,-25.112],[0,0]],"o":[[-7.572,23.922],[39.1,2.628],[3.747,-23.205],[-1.551,-21.421],[1.302,20.55],[0,0]],"v":[[-422.844,149.691],[-346.33,201.239],[-291.136,153.401],[-318.927,-103.718],[-356.793,-99.365],[-335.803,128.38]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.49,"y":0},"t":31,"s":[{"i":[[-3.445,-22.685],[-26.96,2.898],[-3.747,23.205],[0.717,9.899],[-1.591,-25.112],[0,0]],"o":[[3.681,24.243],[30.907,-3.322],[3.747,-23.205],[-1.551,-21.421],[1.302,20.55],[0,0]],"v":[[-406.473,162.277],[-349.257,209.498],[-291.136,153.401],[-318.927,-103.718],[-356.793,-99.365],[-335.803,128.38]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[-3.445,-22.685],[-26.96,2.898],[-3.747,23.205],[0.717,9.899],[-1.591,-25.112],[0,0]],"o":[[3.681,24.243],[30.907,-3.322],[3.747,-23.205],[-1.551,-21.421],[1.302,20.55],[0,0]],"v":[[-406.473,162.277],[-349.257,209.498],[-291.136,153.401],[-318.927,-103.718],[-356.793,-99.365],[-335.803,128.38]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68,"s":[{"i":[[-3.135,-22.73],[-26.96,2.898],[-1.075,25.085],[0.717,9.899],[-1.591,-25.112],[0,0]],"o":[[2.258,16.37],[30.907,-3.322],[1.12,-23.38],[-1.551,-21.421],[1.302,20.55],[0,0]],"v":[[-406.473,162.277],[-333.134,205.433],[-288.448,153.794],[-318.927,-103.718],[-356.793,-99.365],[-335.803,128.38]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[{"i":[[-3.445,-22.685],[-26.96,2.898],[0.707,26.338],[0.717,9.899],[-1.591,-25.112],[0,0]],"o":[[3.681,24.243],[30.907,-3.322],[-0.63,-23.497],[-1.551,-21.421],[1.302,20.55],[0,0]],"v":[[-406.473,162.277],[-327.065,209.619],[-286.656,154.056],[-318.927,-103.718],[-356.793,-99.365],[-335.803,128.38]],"c":false}]},{"i":{"x":0.52,"y":1},"o":{"x":0.167,"y":0.167},"t":75,"s":[{"i":[[-3.445,-22.685],[-27.063,-1.685],[0.707,26.338],[0.717,9.899],[-1.591,-25.112],[0,0]],"o":[[3.681,24.243],[82.005,5.106],[-0.63,-23.497],[-1.551,-21.421],[1.302,20.55],[0,0]],"v":[[-406.473,162.277],[-327.065,209.619],[-286.656,154.056],[-318.927,-103.718],[-356.793,-99.365],[-335.803,128.38]],"c":false}]},{"t":80,"s":[{"i":[[-3.445,-22.685],[-26.96,2.898],[-3.747,23.205],[0.717,9.899],[-1.591,-25.112],[0,0]],"o":[[3.681,24.243],[30.907,-3.322],[3.747,-23.205],[-1.551,-21.421],[1.302,20.55],[0,0]],"v":[[-406.473,162.277],[-349.257,209.498],[-291.136,153.401],[-318.927,-103.718],[-356.793,-99.365],[-335.803,128.38]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.201355982762,0.00595155744,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.89019613827,0.574625172335,0.145098039216,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-8.412,-51.33],[0,0],[0,0]],"o":[[0,0],[11.023,67.257],[0,0],[0,0]],"v":[[-371.841,-7.953],[-357.492,79.812],[-334.582,125.42],[-348.787,-32.425]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.323529411765,0.27827761781,0.052018453561,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529471603,0.352941176471,0.050980395897,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 8","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[6.734,4.412],[6.855,-7.036],[3.385,-3.928],[-1.421,-9.646],[0,0]],"o":[[-5.785,4.934],[-3.6,3.695],[2.146,3.233],[18.512,-15.79],[0,0]],"v":[[-346.562,-30.098],[-396.522,19.754],[-408.603,35.907],[-401.907,47.197],[-342.696,-19.296]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.20000001496,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.89019613827,0.576470588235,0.145098039216,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 12","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[0,0],[0,0],[11.957,-16.427],[-6.274,-16.527]],"o":[[0,0],[0,0],[-0.5,25.981],[6.274,16.527]],"v":[[-318.696,163.484],[-338.274,-35.337],[-406.845,44.241],[-404.712,167.017]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,0],[0,0],[11.957,-16.427],[3.186,-18.493]],"o":[[0,0],[0,0],[-0.5,25.981],[-3.001,17.421]],"v":[[-318.696,163.484],[-338.274,-35.337],[-406.845,44.241],[-426.707,164.958]],"c":false}]},{"i":{"x":0.52,"y":1},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[0,0],[0,0],[11.957,-16.427],[3.186,-18.493]],"o":[[0,0],[0,0],[-0.5,25.981],[-3.001,17.421]],"v":[[-318.696,163.484],[-338.274,-35.337],[-406.845,44.241],[-422.565,150.777]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":31,"s":[{"i":[[0,0],[0,0],[11.957,-16.427],[-6.274,-16.527]],"o":[[0,0],[0,0],[-0.5,25.981],[6.274,16.527]],"v":[[-318.696,163.484],[-338.274,-35.337],[-406.845,44.241],[-404.712,167.017]],"c":false}]},{"i":{"x":0.52,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,0],[0,0],[11.957,-16.427],[-6.274,-16.527]],"o":[[0,0],[0,0],[-0.5,25.981],[6.274,16.527]],"v":[[-318.696,163.484],[-338.274,-35.337],[-406.845,44.241],[-404.712,167.017]],"c":false}]},{"t":80,"s":[{"i":[[0,0],[0,0],[11.957,-16.427],[-6.274,-16.527]],"o":[[0,0],[0,0],[-0.5,25.981],[6.274,16.527]],"v":[[-318.696,163.484],[-338.274,-35.337],[-406.845,44.241],[-404.712,167.017]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.20000001496,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.89019613827,0.576470588235,0.145098039216,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"SAX 2","parent":8,"sr":1,"ks":{"p":{"a":0,"k":[-262.496,-44.339,0]},"a":{"a":0,"k":[-262.496,-44.339,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":0,"s":[{"i":[[7.306,3.381],[22.61,-21.63],[-6.109,-4.337],[-12.627,11.627]],"o":[[-8.441,-3.905],[-16.23,15.527],[6.109,4.337],[16.281,-14.992]],"v":[[-378.554,-97.912],[-437.64,-54.419],[-478.688,0.53],[-419.451,-44.76]],"c":true}]},{"i":{"x":0.44,"y":1},"o":{"x":0.167,"y":0.167},"t":9,"s":[{"i":[[7.408,3.151],[21.926,-22.323],[-6.241,-4.145],[-12.259,12.014]],"o":[[-8.558,-3.641],[-15.739,16.024],[6.241,4.145],[15.806,-15.491]],"v":[[-367.309,-86.43],[-425.013,-41.118],[-464.329,15.082],[-406.532,-32.03]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":20,"s":[{"i":[[7.306,3.381],[22.61,-21.63],[-6.109,-4.337],[-12.627,11.627]],"o":[[-8.441,-3.905],[-16.23,15.527],[6.109,4.337],[16.281,-14.992]],"v":[[-378.554,-97.912],[-437.64,-54.419],[-478.688,0.53],[-419.451,-44.76]],"c":true}]},{"i":{"x":0.43,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[7.306,3.381],[22.61,-21.63],[-6.109,-4.337],[-12.627,11.627]],"o":[[-8.441,-3.905],[-16.23,15.527],[6.109,4.337],[16.281,-14.992]],"v":[[-378.554,-97.912],[-437.64,-54.419],[-478.688,0.53],[-419.451,-44.76]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":90,"s":[{"i":[[3.47,2.622],[13.752,-16.158],[-4.654,-3.506],[-12.364,16.159]],"o":[[-5.192,-3.923],[-17.846,20.969],[5.984,4.507],[15.622,-20.417]],"v":[[-419.66,-128.266],[-465.763,-90.522],[-495.236,-41.2],[-447.291,-79.645]],"c":true}]},{"i":{"x":0.43,"y":1},"o":{"x":0.167,"y":0.167},"t":98,"s":[{"i":[[7.306,3.381],[22.61,-21.63],[-6.109,-4.337],[-12.627,11.627]],"o":[[-8.441,-3.905],[-16.23,15.527],[6.109,4.337],[16.281,-14.992]],"v":[[-378.554,-97.912],[-437.64,-54.419],[-478.688,0.53],[-419.451,-44.76]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":127,"s":[{"i":[[3.47,2.622],[13.752,-16.158],[-4.654,-3.506],[-12.364,16.159]],"o":[[-5.192,-3.923],[-17.846,20.969],[5.984,4.507],[15.622,-20.417]],"v":[[-419.66,-128.266],[-465.763,-90.522],[-495.236,-41.2],[-447.291,-79.645]],"c":true}]},{"i":{"x":0.44,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[7.408,3.151],[21.926,-22.323],[-6.241,-4.145],[-12.259,12.014]],"o":[[-8.558,-3.641],[-15.739,16.024],[6.241,4.145],[15.806,-15.491]],"v":[[-367.309,-86.43],[-425.013,-41.118],[-464.329,15.082],[-406.532,-32.03]],"c":true}]},{"t":140,"s":[{"i":[[7.306,3.381],[22.61,-21.63],[-6.109,-4.337],[-12.627,11.627]],"o":[[-8.441,-3.905],[-16.23,15.527],[6.109,4.337],[16.281,-14.992]],"v":[[-378.554,-97.912],[-437.64,-54.419],[-478.688,0.53],[-419.451,-44.76]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.323529411765,0.27827761781,0.052018453561,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529471603,0.352941176471,0.050980395897,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 5","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":0,"s":[{"i":[[12.419,5.421],[36,-36.167],[-12.528,-10.787],[-37.049,37.888]],"o":[[-12.419,-5.421],[-35.472,35.635],[10.909,9.393],[36.757,-37.59]],"v":[[-361.466,-115.063],[-447.479,-62.407],[-496.484,19.5],[-409.491,-36.553]],"c":true}]},{"i":{"x":0.44,"y":1},"o":{"x":0.167,"y":0.167},"t":9,"s":[{"i":[[12.419,5.421],[36,-36.167],[-12.528,-10.787],[-37.049,37.888]],"o":[[-12.419,-5.421],[-35.472,35.635],[10.909,9.393],[36.757,-37.59]],"v":[[-351.353,-107.199],[-432.202,-53.285],[-488.236,36.01],[-395.827,-25.379]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":20,"s":[{"i":[[12.419,5.421],[36,-36.167],[-12.528,-10.787],[-37.049,37.888]],"o":[[-12.419,-5.421],[-35.472,35.635],[10.909,9.393],[36.757,-37.59]],"v":[[-361.466,-115.063],[-447.479,-62.407],[-496.484,19.5],[-409.491,-36.553]],"c":true}]},{"i":{"x":0.43,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[12.419,5.421],[36,-36.167],[-12.528,-10.787],[-37.049,37.888]],"o":[[-12.419,-5.421],[-35.472,35.635],[10.909,9.393],[36.757,-37.59]],"v":[[-361.466,-115.063],[-447.479,-62.407],[-496.484,19.5],[-409.491,-36.553]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":90,"s":[{"i":[[8.63,7.507],[33.023,-35.499],[-12.528,-10.787],[-37.049,37.888]],"o":[[-10.224,-8.894],[-31.505,33.866],[10.909,9.393],[36.757,-37.59]],"v":[[-398.938,-150.525],[-474.867,-98.22],[-518.15,-13.796],[-438.393,-69.393]],"c":true}]},{"i":{"x":0.43,"y":1},"o":{"x":0.167,"y":0.167},"t":98,"s":[{"i":[[12.419,5.421],[36,-36.167],[-12.528,-10.787],[-37.049,37.888]],"o":[[-12.419,-5.421],[-35.472,35.635],[10.909,9.393],[36.757,-37.59]],"v":[[-361.466,-115.063],[-447.479,-62.407],[-496.484,19.5],[-409.491,-36.553]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.52,"y":0},"t":127,"s":[{"i":[[8.63,7.507],[33.023,-35.499],[-12.528,-10.787],[-37.049,37.888]],"o":[[-10.224,-8.894],[-31.505,33.866],[10.909,9.393],[36.757,-37.59]],"v":[[-398.938,-150.525],[-474.867,-98.22],[-518.15,-13.796],[-438.393,-69.393]],"c":true}]},{"i":{"x":0.44,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[12.419,5.421],[36,-36.167],[-12.528,-10.787],[-37.049,37.888]],"o":[[-12.419,-5.421],[-35.472,35.635],[10.909,9.393],[36.757,-37.59]],"v":[[-351.353,-107.199],[-432.202,-53.285],[-488.236,36.01],[-395.827,-25.379]],"c":true}]},{"t":140,"s":[{"i":[[12.419,5.421],[36,-36.167],[-12.528,-10.787],[-37.049,37.888]],"o":[[-12.419,-5.421],[-35.472,35.635],[10.909,9.393],[36.757,-37.59]],"v":[[-361.466,-115.063],[-447.479,-62.407],[-496.484,19.5],[-409.491,-36.553]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.20000001496,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.96862745098,0.685536343444,0.296286010742,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 4","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"NOSE","parent":18,"sr":1,"ks":{"r":{"a":0,"k":-37},"p":{"a":0,"k":[-194.987,-3.612,0]},"a":{"a":0,"k":[-190.284,-17.401,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[14.5,10.5],[-13.52,-9.152]],"o":[[-15.033,-10.886],[16.25,11]],"v":[[-184,-26],[-197.25,-9]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.996078491211,0.956862804936,0.815686334348,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"EYE01","parent":18,"sr":1,"ks":{"r":{"a":0,"k":-37},"p":{"a":0,"k":[-243.653,3.199,0]},"a":{"a":0,"k":[-233.25,-41.25,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.45,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-220,-54.5],[-224.5,-45.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.49,"y":0},"t":15,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-220,-54.5],[-224.5,-45.5]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-227.346,-48.79],[-231.358,-29.876]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.49,"y":0},"t":50,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-248.295,-4.576],[-240.376,-16.72]],"c":false}]},{"i":{"x":0.53,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-240.805,-23.481],[-233.947,-29.608]],"c":false}]},{"i":{"x":0.53,"y":1},"o":{"x":0.45,"y":0},"t":78,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-220,-54.5],[-224.5,-45.5]],"c":false}]},{"t":140,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-220,-54.5],[-224.5,-45.5]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.45,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-240,-58.5],[-238,-45.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.49,"y":0},"t":15,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-240,-58.5],[-238,-45.5]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-243.774,-50.136],[-243.461,-33.557]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.49,"y":0},"t":50,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-263.085,-22.293],[-248.748,-25.662]],"c":false}]},{"i":{"x":0.53,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-256.154,-25.802],[-244.623,-33.975]],"c":false}]},{"i":{"x":0.53,"y":1},"o":{"x":0.45,"y":0},"t":78,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-240,-58.5],[-238,-45.5]],"c":false}]},{"t":140,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-240,-58.5],[-238,-45.5]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.45,"y":0},"t":0,"s":[{"i":[[0,0],[11.5,0.5],[0,0],[0,0]],"o":[[0,0],[-11.5,-0.5],[0,0],[0,0]],"v":[[-211,-24],[-229.5,-43],[-250,-36.5],[-255.5,-48.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.49,"y":0},"t":15,"s":[{"i":[[0,0],[11.5,0.5],[0,0],[0,0]],"o":[[0,0],[-11.5,-0.5],[0,0],[0,0]],"v":[[-211,-24],[-229.5,-43],[-250,-36.5],[-255.5,-48.5]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[0,0],[12.403,2.397],[0,0],[0,0]],"o":[[0,0],[-11.217,-1.988],[0,0],[0,0]],"v":[[-211.48,-22.907],[-234.166,-31.253],[-252.227,-37.119],[-257.271,-48.393]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.49,"y":0},"t":50,"s":[{"i":[[0,0],[13.476,4.649],[0,0],[0,0]],"o":[[0,0],[-10.882,-3.754],[0,0],[0,0]],"v":[[-212.049,-21.61],[-239.708,-17.302],[-254.871,-37.854],[-266.979,-37.064]],"c":false}]},{"i":{"x":0.53,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,0],[12.488,2.574],[0,0],[0,0]],"o":[[0,0],[-11.191,-2.127],[0,0],[0,0]],"v":[[-213.897,-20.205],[-234.604,-30.151],[-252.436,-37.177],[-265.78,-32.256]],"c":false}]},{"i":{"x":0.53,"y":1},"o":{"x":0.45,"y":0},"t":78,"s":[{"i":[[0,0],[11.5,0.5],[0,0],[0,0]],"o":[[0,0],[-11.5,-0.5],[0,0],[0,0]],"v":[[-211,-24],[-229.5,-43],[-250,-36.5],[-255.5,-48.5]],"c":false}]},{"t":140,"s":[{"i":[[0,0],[11.5,0.5],[0,0],[0,0]],"o":[[0,0],[-11.5,-0.5],[0,0],[0,0]],"v":[[-211,-24],[-229.5,-43],[-250,-36.5],[-255.5,-48.5]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"EYE02","parent":18,"sr":1,"ks":{"r":{"a":0,"k":-37},"p":{"a":0,"k":[-140.265,-2.085,0]},"a":{"a":0,"k":[-147.5,16.75,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.45,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-144,6],[-131.5,1]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.49,"y":0},"t":15,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-144,6],[-131.5,1]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-153.795,16.917],[-141.004,16.161]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.49,"y":0},"t":50,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-167.324,31.85],[-171.057,48.282]],"c":false}]},{"i":{"x":0.53,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-157.844,19.854],[-162.977,33.489]],"c":false}]},{"i":{"x":0.53,"y":1},"o":{"x":0.45,"y":0},"t":78,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-144,6],[-131.5,1]],"c":false}]},{"t":140,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-144,6],[-131.5,1]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.45,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-140,18.5],[-125.5,17]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.49,"y":0},"t":15,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-140,18.5],[-125.5,17]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-145.48,26.651],[-130.61,27.039]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.49,"y":0},"t":50,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-155.68,37.481],[-153.826,52.815]],"c":false}]},{"i":{"x":0.53,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-147.84,27.99],[-151.826,42.665]],"c":false}]},{"i":{"x":0.53,"y":1},"o":{"x":0.45,"y":0},"t":78,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-140,18.5],[-125.5,17]],"c":false}]},{"t":140,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-140,18.5],[-125.5,17]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.45,"y":0},"t":0,"s":[{"i":[[0,0],[-8.5,-9.5],[0,0],[0,0]],"o":[[0,0],[8.5,9.5],[0,0],[0,0]],"v":[[-170.5,-4],[-146.5,5.5],[-139,32],[-124.5,37.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.49,"y":0},"t":15,"s":[{"i":[[0,0],[-8.5,-9.5],[0,0],[0,0]],"o":[[0,0],[8.5,9.5],[0,0],[0,0]],"v":[[-170.5,-4],[-146.5,5.5],[-139,32],[-124.5,37.5]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[0,0],[-8.5,-9.5],[0,0],[0,0]],"o":[[0,0],[8.5,9.5],[0,0],[0,0]],"v":[[-173.951,-0.279],[-156.359,15.458],[-140.54,34.229],[-125.205,39.89]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.49,"y":0},"t":50,"s":[{"i":[[0,0],[-8.5,-9.5],[0,0],[0,0]],"o":[[0,0],[8.5,9.5],[0,0],[0,0]],"v":[[-178.048,4.141],[-168.066,27.284],[-142.369,36.876],[-135.514,49.245]],"c":false}]},{"i":{"x":0.53,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,0],[-10.094,-9.422],[0,0],[0,0]],"o":[[0,0],[9.229,8.73],[0,0],[0,0]],"v":[[-179.506,4.532],[-157.6,18.675],[-140.685,34.438],[-137.41,48.587]],"c":false}]},{"i":{"x":0.53,"y":1},"o":{"x":0.45,"y":0},"t":78,"s":[{"i":[[0,0],[-8.5,-9.5],[0,0],[0,0]],"o":[[0,0],[8.5,9.5],[0,0],[0,0]],"v":[[-170.5,-4],[-146.5,5.5],[-139,32],[-124.5,37.5]],"c":false}]},{"t":140,"s":[{"i":[[0,0],[-8.5,-9.5],[0,0],[0,0]],"o":[[0,0],[8.5,9.5],[0,0],[0,0]],"v":[[-170.5,-4],[-146.5,5.5],[-139,32],[-124.5,37.5]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"neckle","parent":14,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.42],"y":[0]},"t":0,"s":[2]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[-8.5]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.42],"y":[0]},"t":50,"s":[-19]},{"i":{"x":[0.26],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":65,"s":[-8.5]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.69],"y":[0]},"t":90,"s":[2]},{"i":{"x":[0.54],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":103,"s":[2]},{"i":{"x":[0.54],"y":[1]},"o":{"x":[0.43],"y":[0]},"t":127,"s":[2]},{"t":140,"s":[2]}]},"p":{"a":0,"k":[-237.347,93.788,0]},"a":{"a":0,"k":[-240.005,91.093,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[2.755,14.413],[-2.455,-13.566]],"o":[[-2.33,-12.192],[2.79,15.412]],"v":[[-249.593,50.114],[-269.967,54.077]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.658665615905,0.283944612391,0.73137254902,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.974163758521,0.843137254902,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 5","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[11.003,-4.604],[-11.334,3.218]],"o":[[-10.419,4.36],[12.867,-3.653]],"v":[[-197.482,85.535],[-191.144,104.559]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.658665615905,0.283944612391,0.73137254902,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.974163758521,0.843137254902,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 4","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[12.5,-8.25],[-11.555,9.002]],"o":[[-9.78,6.455],[9.079,-7.073]],"v":[[-211,70.5],[-198.206,85.749]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.658665615905,0.283944612391,0.73137254902,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.974163758521,0.843137254902,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[6.75,-11.25],[-6.643,11.811]],"o":[[-7.213,12.022],[6.75,-12]],"v":[[-229,59.5],[-211.5,69.75]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.658665615905,0.283944612391,0.73137254902,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.974163758521,0.843137254902,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[6.393,-10.388],[-5.739,13.894]],"o":[[-6.824,11.088],[5.061,-12.253]],"v":[[-247.724,50.595],[-228.812,58.852]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.658665615905,0.283944612391,0.73137254902,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.974163758521,0.843137254902,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"BODY","parent":1,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.42],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[-19]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.42],"y":[0]},"t":50,"s":[-74]},{"i":{"x":[0.26],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":65,"s":[-37]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.69],"y":[0]},"t":90,"s":[7]},{"i":{"x":[0.54],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":103,"s":[-3]},{"i":{"x":[0.54],"y":[1]},"o":{"x":[0.43],"y":[0]},"t":127,"s":[7]},{"t":140,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.42,"y":0},"t":0,"s":[90.3,150.487,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":15,"s":[107.796,171.989,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.42,"y":0},"t":50,"s":[130.3,91.987,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.26,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[110.3,156.237,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.69,"y":0},"t":90,"s":[69.8,142.487,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.54,"y":1},"o":{"x":0.167,"y":0.167},"t":103,"s":[90.3,150.487,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.54,"y":1},"o":{"x":0.43,"y":0},"t":127,"s":[69.8,142.487,0],"to":[0,0,0],"ti":[0,0,0]},{"t":140,"s":[90.3,150.487,0]}]},"a":{"a":0,"k":[-248.5,139.5,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.42,"y":0},"t":0,"s":[{"i":[[9,8.5],[15,-27.5],[0,0],[-17,-6],[0,0]],"o":[[-0.5,26],[18,-13.5],[0,0],[-2.5,-12.5],[0,0]],"v":[[-220,77.5],[-250.5,184.5],[-226.5,166],[-196,181],[-208,95.5]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[9,8.5],[15,-27.5],[0,0],[-9.972,-8.35],[0,0]],"o":[[-0.5,26],[18,-13.5],[0,0],[2.592,-15.658],[0,0]],"v":[[-220,77.5],[-256.809,182.857],[-228.259,168.038],[-203.936,184.084],[-208,95.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.42,"y":0},"t":50,"s":[{"i":[[9,8.5],[15,-27.5],[0,0],[-8.404,-9.18],[0,0]],"o":[[-0.5,26],[26.547,-3.333],[0,0],[26.93,-47.4],[0,0]],"v":[[-220,77.5],[-285.311,183.227],[-249.542,177.262],[-233.114,196.612],[-208,95.5]],"c":true}]},{"i":{"x":0.26,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[9,8.5],[15,-27.5],[0,0],[-12.702,-7.59],[0,0]],"o":[[-0.5,26],[22.273,-8.417],[0,0],[12.215,-29.95],[0,0]],"v":[[-220,77.5],[-267.905,183.864],[-238.021,171.631],[-214.557,188.806],[-208,95.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.69,"y":0},"t":90,"s":[{"i":[[9,8.5],[6.196,-35.44],[0,0],[-19.294,3.287],[0,0]],"o":[[-0.5,26],[12.343,-10],[0,0],[-11.623,-16.294],[0,0]],"v":[[-220,77.5],[-241.019,187.87],[-224.332,167.245],[-186.318,177.796],[-208,95.5]],"c":true}]},{"i":{"x":0.54,"y":1},"o":{"x":0.167,"y":0.167},"t":103,"s":[{"i":[[9,8.5],[15,-27.5],[0,0],[-17,-6],[0,0]],"o":[[-0.5,26],[18,-13.5],[0,0],[-2.5,-12.5],[0,0]],"v":[[-220,77.5],[-250.5,184.5],[-226.5,166],[-196,181],[-208,95.5]],"c":true}]},{"i":{"x":0.54,"y":1},"o":{"x":0.43,"y":0},"t":127,"s":[{"i":[[9,8.5],[6.196,-35.44],[0,0],[-19.294,3.287],[0,0]],"o":[[-0.5,26],[12.343,-10],[0,0],[-11.623,-16.294],[0,0]],"v":[[-220,77.5],[-241.019,187.87],[-224.332,167.245],[-186.318,177.796],[-208,95.5]],"c":true}]},{"t":140,"s":[{"i":[[9,8.5],[15,-27.5],[0,0],[-17,-6],[0,0]],"o":[[-0.5,26],[18,-13.5],[0,0],[-2.5,-12.5],[0,0]],"v":[[-220,77.5],[-250.5,184.5],[-226.5,166],[-196,181],[-208,95.5]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.625490196078,0.114840443929,0.06377546273,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.800000059838,0.133333333333,0.066666666667,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.42,"y":0},"t":0,"s":[{"i":[[18.984,-11.783],[27,-41],[0,0],[-5,-8],[-8,4.5],[-4,-3.5],[-7,7.5],[-11,0.5],[3.5,27.5]],"o":[[-14.5,9],[16.5,0],[0,0],[15,-5],[6.5,13],[13.5,-7.5],[12,8],[-4.5,-21],[-2.22,-17.445]],"v":[[-257.5,70.5],[-331,159.5],[-302,155.5],[-295.5,178],[-266,165],[-251.5,189.5],[-227.5,170.5],[-190.5,187.5],[-205,90]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[18.984,-11.783],[38.785,-28.18],[0,0],[3.578,-12.206],[-10.29,3.003],[-4,-3.5],[-8.884,7.542],[-8.947,-3.085],[3.5,27.5]],"o":[[-14.5,9],[18.671,7.199],[0,0],[15.187,-2.921],[4.497,10.739],[13.519,-9.892],[12,8],[-0.544,-21.344],[-2.22,-17.445]],"v":[[-257.5,70.5],[-332.432,128.336],[-307.207,138.371],[-311.853,168.668],[-279.075,159.969],[-262.599,192.553],[-230.988,171.414],[-198.274,190.111],[-205,90]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.42,"y":0},"t":50,"s":[{"i":[[18.984,-11.783],[41.952,-6.447],[0,0],[10.416,-12.428],[-11.711,2.579],[3.012,-11.903],[-13.562,5.444],[-3.069,-5.877],[3.5,27.5]],"o":[[-14.5,9],[12.304,17.139],[0,0],[13.596,9.547],[-1.469,11.086],[19.643,-1.067],[8.285,12.703],[15.944,-17.57],[-2.22,-17.445]],"v":[[-257.5,70.5],[-334.52,99.286],[-308.115,122.507],[-324.783,148.969],[-285.464,157.64],[-291.175,187.223],[-250.552,181.186],[-234.113,202.989],[-205,90]],"c":true}]},{"i":{"x":0.26,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[18.984,-11.783],[34.476,-23.723],[0,0],[2.708,-10.214],[-9.868,-0.004],[-0.494,-7.701],[-10.281,6.472],[-7.034,-2.688],[3.5,27.5]],"o":[[-14.5,9],[14.402,8.569],[0,0],[14.298,2.273],[4.305,11.928],[16.571,-4.284],[10.143,10.352],[5.722,-19.285],[-2.22,-17.445]],"v":[[-257.5,70.5],[-335.527,114.787],[-306.134,128.801],[-318.33,158.566],[-281.635,162.507],[-271.337,188.361],[-239.026,175.843],[-212.306,195.244],[-205,90]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.69,"y":0},"t":90,"s":[{"i":[[18.984,-11.783],[27,-41],[0,0],[-5,-8],[-8,4.5],[-4,-3.5],[-7,7.5],[-10.62,9.656],[3.5,27.5]],"o":[[-14.5,9],[15.111,-2.658],[0,0],[15,-5],[6.5,13],[7.065,-8.687],[12,8],[-10.193,-15.585],[-2.22,-17.445]],"v":[[-257.5,70.5],[-323.939,163.671],[-294.504,159.113],[-289.371,182.789],[-260.672,167.368],[-241.34,194.298],[-222.233,172.372],[-180.496,178.715],[-205,90]],"c":true}]},{"i":{"x":0.54,"y":1},"o":{"x":0.167,"y":0.167},"t":103,"s":[{"i":[[18.984,-11.783],[27,-41],[0,0],[-5,-8],[-8,4.5],[-4,-3.5],[-7,7.5],[-11,0.5],[3.5,27.5]],"o":[[-14.5,9],[16.5,0],[0,0],[15,-5],[6.5,13],[13.5,-7.5],[12,8],[-4.5,-21],[-2.22,-17.445]],"v":[[-257.5,70.5],[-331,159.5],[-302,155.5],[-295.5,178],[-266,165],[-251.5,189.5],[-227.5,170.5],[-190.5,187.5],[-205,90]],"c":true}]},{"i":{"x":0.54,"y":1},"o":{"x":0.43,"y":0},"t":127,"s":[{"i":[[18.984,-11.783],[27,-41],[0,0],[-5,-8],[-8,4.5],[-4,-3.5],[-7,7.5],[-10.62,9.656],[3.5,27.5]],"o":[[-14.5,9],[15.111,-2.658],[0,0],[15,-5],[6.5,13],[7.065,-8.687],[12,8],[-10.193,-15.585],[-2.22,-17.445]],"v":[[-257.5,70.5],[-323.939,163.671],[-294.504,159.113],[-289.371,182.789],[-260.672,167.368],[-241.34,194.298],[-222.233,172.372],[-180.496,178.715],[-205,90]],"c":true}]},{"t":140,"s":[{"i":[[18.984,-11.783],[27,-41],[0,0],[-5,-8],[-8,4.5],[-4,-3.5],[-7,7.5],[-11,0.5],[3.5,27.5]],"o":[[-14.5,9],[16.5,0],[0,0],[15,-5],[6.5,13],[13.5,-7.5],[12,8],[-4.5,-21],[-2.22,-17.445]],"v":[[-257.5,70.5],[-331,159.5],[-302,155.5],[-295.5,178],[-266,165],[-251.5,189.5],[-227.5,170.5],[-190.5,187.5],[-205,90]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.625490196078,0.114840443929,0.06377546273,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.266666666667,0.20000001496,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"LEG01","parent":1,"sr":1,"ks":{"p":{"a":0,"k":[32.297,228.51,0]},"a":{"a":0,"k":[-306.503,217.523,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.42,"y":0},"t":0,"s":[{"i":[[0,0],[-0.5,-9],[-5.5,1],[-1.342,2.683],[0,0]],"o":[[0,0],[0.5,9],[5.5,-1],[0,-1],[0,0]],"v":[[-303,136.5],[-335,177.5],[-297.5,217.5],[-306,182],[-278,158]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[0,0],[-0.5,-9],[-5.5,1],[-1.342,2.683],[0,0]],"o":[[0,0],[0.5,9],[5.5,-1],[0,-1],[0,0]],"v":[[-290,156],[-318.5,191.5],[-297.5,217.5],[-289,195.5],[-263.5,174.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.42,"y":0},"t":50,"s":[{"i":[[0,0],[9.481,-17.751],[-5.5,1],[-15.125,18.5],[0,0]],"o":[[0,0],[-5.875,11],[5.5,-1],[19.125,-19],[0,0]],"v":[[-262,127.25],[-275,173.75],[-297.5,217.5],[-252.375,188],[-215,125]],"c":true}]},{"i":{"x":0.26,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,0],[4.49,-13.376],[-5.5,1],[-8.313,11.5],[0,0]],"o":[[0,0],[-2.688,10],[5.5,-1],[11.188,-7],[0,0]],"v":[[-280.5,143.375],[-307,182.125],[-297.5,217.5],[-278.688,194.5],[-251.5,156.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.69,"y":0},"t":90,"s":[{"i":[[0,0],[-0.5,-9],[-5.5,1],[-1.342,2.683],[0,0]],"o":[[0,0],[0.5,9],[5.5,-1],[0,-1],[0,0]],"v":[[-317.5,129.5],[-339.5,177.5],[-297.5,217.5],[-307.5,179.5],[-284,144.5]],"c":true}]},{"i":{"x":0.54,"y":1},"o":{"x":0.167,"y":0.167},"t":103,"s":[{"i":[[0,0],[-0.5,-9],[-5.5,1],[-1.342,2.683],[0,0]],"o":[[0,0],[0.5,9],[5.5,-1],[0,-1],[0,0]],"v":[[-303,136.5],[-335,177.5],[-297.5,217.5],[-306,182],[-278,158]],"c":true}]},{"i":{"x":0.54,"y":1},"o":{"x":0.43,"y":0},"t":127,"s":[{"i":[[0,0],[-0.5,-9],[-5.5,1],[-1.342,2.683],[0,0]],"o":[[0,0],[0.5,9],[5.5,-1],[0,-1],[0,0]],"v":[[-317.5,129.5],[-339.5,177.5],[-297.5,217.5],[-307.5,179.5],[-284,144.5]],"c":true}]},{"t":140,"s":[{"i":[[0,0],[-0.5,-9],[-5.5,1],[-1.342,2.683],[0,0]],"o":[[0,0],[0.5,9],[5.5,-1],[0,-1],[0,0]],"v":[[-303,136.5],[-335,177.5],[-297.5,217.5],[-306,182],[-278,158]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.662745098039,0.282352941176,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.960784373564,0.694117647059,0.152941176471,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"LEG02","parent":1,"sr":1,"ks":{"p":{"a":0,"k":[150.788,232.005,0]},"a":{"a":0,"k":[-188.012,221.017,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.42,"y":0},"t":0,"s":[{"i":[[3.5,12.5],[0,0],[-11.809,-13.213],[-5.206,0.651]],"o":[[-14,0],[0,0],[14.389,16.1],[8,-1]],"v":[[-204,159.5],[-248.5,162.5],[-226.73,187.84],[-190,221]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[18,22.5],[0,0],[-1.047,-3.858],[-4.885,0.611]],"o":[[-14,0],[0,0],[1.494,5.502],[8,-1]],"v":[[-175,174.5],[-212.5,168.5],[-194.494,196.498],[-190,221]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.42,"y":0},"t":50,"s":[{"i":[[1.5,35],[0,0],[-1.279,-13.377],[-4.885,0.611]],"o":[[-14,0],[0,0],[1.244,13.002],[8,-1]],"v":[[-160.75,91.5],[-197,108.25],[-197.494,161.248],[-190,221]],"c":true}]},{"i":{"x":0.26,"y":1},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[2.5,23.75],[0,0],[-4.888,-11.544],[-5.045,0.631]],"o":[[-14,0],[0,0],[5.612,14.456],[8,-1]],"v":[[-180.375,155.5],[-210.25,156.875],[-206.612,193.544],[-190,221]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.69,"y":0},"t":90,"s":[{"i":[[8.5,24.5],[0,0],[-9.571,-9.841],[-5.206,0.651]],"o":[[-14,0],[0,0],[15.23,15.66],[8,-1]],"v":[[-220,156],[-263,156],[-233.73,192.34],[-190,221]],"c":true}]},{"i":{"x":0.54,"y":1},"o":{"x":0.167,"y":0.167},"t":103,"s":[{"i":[[3.5,12.5],[0,0],[-11.809,-13.213],[-5.206,0.651]],"o":[[-14,0],[0,0],[14.389,16.1],[8,-1]],"v":[[-204,159.5],[-248.5,162.5],[-226.73,187.84],[-190,221]],"c":true}]},{"i":{"x":0.54,"y":1},"o":{"x":0.43,"y":0},"t":127,"s":[{"i":[[8.5,24.5],[0,0],[-9.571,-9.841],[-5.206,0.651]],"o":[[-14,0],[0,0],[15.23,15.66],[8,-1]],"v":[[-220,156],[-263,156],[-233.73,192.34],[-190,221]],"c":true}]},{"t":140,"s":[{"i":[[3.5,12.5],[0,0],[-11.809,-13.213],[-5.206,0.651]],"o":[[-14,0],[0,0],[14.389,16.1],[8,-1]],"v":[[-204,159.5],[-248.5,162.5],[-226.73,187.84],[-190,221]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.662745098039,0.282352941176,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.960784373564,0.694117647059,0.152941176471,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"HAND01","parent":14,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.48],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[19]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":54,"s":[-8]},{"i":{"x":[0.52],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":71,"s":[-8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.48],"y":[0]},"t":90,"s":[8]},{"i":{"x":[0.52],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":103,"s":[0]},{"i":{"x":[0.52],"y":[1]},"o":{"x":[0.48],"y":[0]},"t":127,"s":[8]},{"t":140,"s":[0]}]},"p":{"a":0,"k":[-255.072,98.795,0]},"a":{"a":0,"k":[-255.072,98.795,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[4.5,-7.5],[-8,-1.5],[4.712,6.951]],"o":[[-4.5,7.5],[9.25,-4.5],[-6.288,-5.299]],"v":[[-329.25,84.5],[-266,116],[-256.962,91.049]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.662745098039,0.282352941176,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.960784373564,0.694117647059,0.152941176471,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":"STAR","parent":14,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.42],"y":[0]},"t":0,"s":[37]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[44.5]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.42],"y":[0]},"t":50,"s":[14]},{"i":{"x":[0.26],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":65,"s":[25.5]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.69],"y":[0]},"t":90,"s":[49]},{"i":{"x":[0.54],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":103,"s":[37]},{"i":{"x":[0.54],"y":[1]},"o":{"x":[0.43],"y":[0]},"t":127,"s":[49]},{"t":140,"s":[37]}]},"p":{"a":0,"k":[-223.048,55.886,0]},"a":{"a":0,"k":[-177.048,74.636,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.53,"y":1},"o":{"x":0.48,"y":0},"t":0,"s":[{"i":[[0,0],[-25.053,20.757]],"o":[[0,0],[17.412,-14.427]],"v":[[-208.325,-94.481],[-169.735,-142.969]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.47,"y":0},"t":15,"s":[{"i":[[0,0],[-25.053,20.757]],"o":[[0,0],[17.412,-14.427]],"v":[[-208.325,-94.481],[-162.084,-145.51]],"c":false}]},{"i":{"x":0.53,"y":1},"o":{"x":0.167,"y":0.167},"t":22,"s":[{"i":[[0,0],[-48.726,14.642]],"o":[[0,0],[20.425,-6.138]],"v":[[-213.192,-91.742],[-134.129,-150.285]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.47,"y":0},"t":54,"s":[{"i":[[0,0],[-14.235,11.179]],"o":[[0,0],[12.159,-9.549]],"v":[[-213.723,-114.484],[-190.154,-151.294]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":71,"s":[{"i":[[0,0],[-7.356,9.68]],"o":[[0,0],[10.917,-14.366]],"v":[[-223.009,-107.222],[-225.64,-137.167]],"c":false}]},{"i":{"x":0.53,"y":1},"o":{"x":0.167,"y":0.167},"t":86,"s":[{"i":[[0,0],[-25.053,20.757]],"o":[[0,0],[17.412,-14.427]],"v":[[-208.325,-94.481],[-169.735,-142.969]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.47,"y":0},"t":93,"s":[{"i":[[0,0],[-25.053,20.757]],"o":[[0,0],[17.412,-14.427]],"v":[[-208.325,-94.481],[-162.084,-145.51]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":100,"s":[{"i":[[0,0],[-39.059,24.296]],"o":[[0,0],[19.201,-11.943]],"v":[[-210.95,-88.535],[-155.775,-143.23]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103,"s":[{"i":[[0,0],[-25.053,20.757]],"o":[[0,0],[17.412,-14.427]],"v":[[-208.325,-94.481],[-169.735,-142.969]],"c":false}]},{"i":{"x":0.52,"y":1},"o":{"x":0.167,"y":0.167},"t":114,"s":[{"i":[[0,0],[-25.053,20.757]],"o":[[0,0],[17.412,-14.427]],"v":[[-212.861,-95.301],[-183.587,-146.521]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.48,"y":0},"t":127,"s":[{"i":[[0,0],[-25.053,20.757]],"o":[[0,0],[17.412,-14.427]],"v":[[-208.325,-94.481],[-169.735,-142.969]],"c":false}]},{"i":{"x":0.53,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[0,0],[-39.059,24.296]],"o":[[0,0],[19.201,-11.943]],"v":[[-210.95,-88.535],[-155.775,-143.23]],"c":false}]},{"t":140,"s":[{"i":[[0,0],[-25.053,20.757]],"o":[[0,0],[17.412,-14.427]],"v":[[-208.325,-94.481],[-169.735,-142.969]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 4","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.53,"y":1},"o":{"x":0.48,"y":0},"t":0,"s":[{"i":[[0,0],[-0.77,13.102],[0,0]],"o":[[0,0],[6.745,-2.265],[0,0]],"v":[[-281.119,-47.766],[-287.085,-76.452],[-274.289,-78.581]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.47,"y":0},"t":15,"s":[{"i":[[0,0],[-0.77,13.102],[0,0]],"o":[[0,0],[6.745,-2.265],[0,0]],"v":[[-279.446,-53.549],[-283.78,-85.784],[-267.263,-83.594]],"c":false}]},{"i":{"x":0.53,"y":1},"o":{"x":0.167,"y":0.167},"t":22,"s":[{"i":[[0,0],[-7.397,11.277],[0,0]],"o":[[0,0],[5.72,4.859],[0,0]],"v":[[-281.643,-68.332],[-271.012,-101.25],[-252.985,-86.802]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.47,"y":0},"t":54,"s":[{"i":[[0,0],[-10.002,2.711],[0,0]],"o":[[0,0],[10.572,-1.867],[0,0]],"v":[[-300.271,-55.145],[-299.592,-70.79],[-264.508,-75.034]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":71,"s":[{"i":[[0,0],[-1.585,4.099],[0,0]],"o":[[0,0],[22.471,-16.932],[0,0]],"v":[[-314.495,-47.565],[-321.711,-59.919],[-267.613,-73.803]],"c":false}]},{"i":{"x":0.53,"y":1},"o":{"x":0.167,"y":0.167},"t":86,"s":[{"i":[[0,0],[-0.77,13.102],[0,0]],"o":[[0,0],[6.745,-2.265],[0,0]],"v":[[-281.119,-47.766],[-287.085,-76.452],[-274.289,-78.581]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.47,"y":0},"t":93,"s":[{"i":[[0,0],[-0.77,13.102],[0,0]],"o":[[0,0],[6.745,-2.265],[0,0]],"v":[[-279.446,-53.549],[-283.78,-85.784],[-267.263,-83.594]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":100,"s":[{"i":[[0,0],[-4.414,8.709],[0,0]],"o":[[0,0],[7.091,1.218],[0,0]],"v":[[-276.654,-52.064],[-268.74,-93.186],[-252.674,-87.118]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103,"s":[{"i":[[0,0],[-0.77,13.102],[0,0]],"o":[[0,0],[6.745,-2.265],[0,0]],"v":[[-281.119,-47.766],[-287.085,-76.452],[-274.289,-78.581]],"c":false}]},{"i":{"x":0.52,"y":1},"o":{"x":0.167,"y":0.167},"t":114,"s":[{"i":[[0,0],[-0.77,13.102],[0,0]],"o":[[0,0],[6.745,-2.265],[0,0]],"v":[[-289.677,-44.023],[-297.194,-71.446],[-277.923,-74.333]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.48,"y":0},"t":127,"s":[{"i":[[0,0],[-0.77,13.102],[0,0]],"o":[[0,0],[6.745,-2.265],[0,0]],"v":[[-281.119,-47.766],[-287.085,-76.452],[-274.289,-78.581]],"c":false}]},{"i":{"x":0.53,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[0,0],[-4.414,8.709],[0,0]],"o":[[0,0],[7.091,1.218],[0,0]],"v":[[-276.654,-52.064],[-268.74,-93.186],[-252.674,-87.118]],"c":false}]},{"t":140,"s":[{"i":[[0,0],[-0.77,13.102],[0,0]],"o":[[0,0],[6.745,-2.265],[0,0]],"v":[[-281.119,-47.766],[-287.085,-76.452],[-274.289,-78.581]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.53,"y":1},"o":{"x":0.48,"y":0},"t":0,"s":[{"i":[[13.068,-2.221],[-13.83,-13.369],[27.214,-15.812],[-0.311,-26.999],[25.003,1.193],[-17.437,5.94],[16.205,24.413],[-6.645,24.415]],"o":[[15.538,6.873],[-8.369,13.819],[10.277,11.977],[-9.783,7.372],[13.677,11.919],[3.386,-6.308],[20.906,-19.198],[-11.825,-11.123]],"v":[[-107.377,-85.519],[-62.992,-57.811],[-112.884,-10.198],[-91.417,63.154],[-158.029,79.855],[-64.424,81.943],[-86.646,6.343],[-38.362,-66.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.47,"y":0},"t":15,"s":[{"i":[[13.068,-2.221],[-13.83,-13.369],[27.214,-15.812],[-0.311,-26.999],[25.003,1.193],[-17.437,5.94],[16.205,24.413],[-6.645,24.415]],"o":[[15.538,6.873],[-8.369,13.819],[10.277,11.977],[-9.783,7.372],[13.677,11.919],[3.386,-6.308],[20.906,-19.198],[-11.825,-11.123]],"v":[[-107.377,-85.519],[-55.361,-59.235],[-112.884,-10.198],[-91.417,63.154],[-158.029,79.855],[-54.822,70.715],[-86.646,6.343],[-31.891,-65.876]],"c":true}]},{"i":{"x":0.53,"y":1},"o":{"x":0.167,"y":0.167},"t":22,"s":[{"i":[[16.173,-4.721],[-13.559,-11.473],[26.577,-17.442],[-0.838,-25.328],[25.003,1.193],[-18.308,7.285],[16.205,24.413],[-21.525,11.866]],"o":[[16.558,3.701],[-7.728,14.816],[10.277,11.977],[-9.783,7.372],[13.677,11.919],[2.125,-7.064],[17.359,-28.944],[-18.563,-23.795]],"v":[[-109.607,-87.425],[-57.826,-65.106],[-112.884,-10.198],[-79.351,65.573],[-158.315,79.288],[-42.844,82.437],[-86.378,4.844],[-16.295,-62.572]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.47,"y":0},"t":54,"s":[{"i":[[6.674,-9.814],[-12.32,-2.805],[23.664,-24.893],[-3.246,-17.686],[25.003,1.193],[-22.293,13.436],[16.205,24.413],[0.804,27.895]],"o":[[21.218,-10.802],[-4.797,19.374],[10.277,11.977],[-9.783,7.372],[13.677,11.919],[-3.637,-10.521],[20.906,-19.198],[-28.926,-8.675]],"v":[[-113.356,-86.23],[-69.092,-91.947],[-112.884,-10.198],[-80.265,59.044],[-159.619,76.697],[-52.832,65.341],[-85.151,-2.01],[-51.942,-107.171]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":71,"s":[{"i":[[12.562,-27.805],[-9.136,-10.182],[24.947,-21.611],[-2.743,-22.887],[25.003,1.193],[-19.084,29.592],[27.308,-1.699],[11.781,43.315]],"o":[[17.118,-14.557],[0.011,20.585],[17.346,10.396],[-9.783,7.372],[13.677,11.919],[-1.099,-8.998],[20.906,-19.198],[-22.746,-9.56]],"v":[[-111.195,-85.973],[-71.193,-90.812],[-108.542,-1.689],[-73.486,44.055],[-159.044,77.838],[-44.199,43.379],[-86.123,5.46],[-50.117,-111.703]],"c":true}]},{"i":{"x":0.53,"y":1},"o":{"x":0.167,"y":0.167},"t":86,"s":[{"i":[[13.068,-2.221],[-13.83,-13.369],[27.214,-15.812],[-0.311,-26.999],[25.003,1.193],[-17.437,5.94],[16.205,24.413],[-6.645,24.415]],"o":[[15.538,6.873],[-8.369,13.819],[10.277,11.977],[-9.783,7.372],[13.677,11.919],[3.386,-6.308],[20.906,-19.198],[-11.825,-11.123]],"v":[[-107.377,-85.519],[-62.992,-57.811],[-112.884,-10.198],[-91.417,63.154],[-158.029,79.855],[-64.424,81.943],[-86.646,6.343],[-38.362,-66.667]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.47,"y":0},"t":93,"s":[{"i":[[13.068,-2.221],[-13.83,-13.369],[27.214,-15.812],[-0.311,-26.999],[25.003,1.193],[-17.437,5.94],[16.205,24.413],[-6.645,24.415]],"o":[[15.538,6.873],[-8.369,13.819],[10.277,11.977],[-9.783,7.372],[13.677,11.919],[3.386,-6.308],[20.906,-19.198],[-11.825,-11.123]],"v":[[-107.377,-85.519],[-55.361,-59.235],[-112.884,-10.198],[-91.417,63.154],[-158.029,79.855],[-59.989,79.911],[-86.646,6.343],[-31.891,-65.876]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":100,"s":[{"i":[[13.068,-2.221],[-13.83,-13.369],[31.595,-11.098],[-0.311,-26.999],[25.003,1.193],[-17.437,5.94],[16.205,24.413],[-6.645,24.415]],"o":[[15.538,6.873],[-8.369,13.819],[10.277,11.977],[-9.783,7.372],[13.677,11.919],[3.386,-6.308],[22.763,-16.59],[-16.315,-19.308]],"v":[[-106.962,-87.984],[-47.419,-51.608],[-108.351,-7.608],[-87.265,66.795],[-158.029,79.855],[-57.025,87.989],[-84.905,7.448],[-22.57,-56.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103,"s":[{"i":[[13.068,-2.221],[-13.83,-13.369],[27.214,-15.812],[-0.311,-26.999],[25.003,1.193],[-17.437,5.94],[16.205,24.413],[-6.645,24.415]],"o":[[15.538,6.873],[-8.369,13.819],[10.277,11.977],[-9.783,7.372],[13.677,11.919],[3.386,-6.308],[20.906,-19.198],[-11.825,-11.123]],"v":[[-107.377,-85.519],[-62.992,-57.811],[-112.884,-10.198],[-91.417,63.154],[-158.029,79.855],[-64.424,81.943],[-86.646,6.343],[-38.362,-66.667]],"c":true}]},{"i":{"x":0.52,"y":1},"o":{"x":0.167,"y":0.167},"t":114,"s":[{"i":[[13.068,-2.221],[-13.83,-13.369],[27.214,-15.812],[-0.311,-26.999],[25.003,1.193],[-17.437,5.94],[22.339,26.512],[-6.645,24.415]],"o":[[15.538,6.873],[-8.369,13.819],[10.277,11.977],[-9.783,7.372],[13.677,11.919],[3.386,-6.308],[20.906,-19.198],[-11.825,-11.123]],"v":[[-107.377,-85.519],[-65.887,-67.704],[-112.884,-10.198],[-88.973,56.652],[-158.029,79.855],[-60.457,70.976],[-86.646,6.343],[-44.386,-77.236]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.48,"y":0},"t":127,"s":[{"i":[[13.068,-2.221],[-13.83,-13.369],[27.214,-15.812],[-0.311,-26.999],[25.003,1.193],[-17.437,5.94],[16.205,24.413],[-6.645,24.415]],"o":[[15.538,6.873],[-8.369,13.819],[10.277,11.977],[-9.783,7.372],[13.677,11.919],[3.386,-6.308],[20.906,-19.198],[-11.825,-11.123]],"v":[[-107.377,-85.519],[-62.992,-57.811],[-112.884,-10.198],[-91.417,63.154],[-158.029,79.855],[-64.424,81.943],[-86.646,6.343],[-38.362,-66.667]],"c":true}]},{"i":{"x":0.53,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[13.068,-2.221],[-13.83,-13.369],[31.595,-11.098],[-0.311,-26.999],[25.003,1.193],[-17.437,5.94],[16.205,24.413],[-6.645,24.415]],"o":[[15.538,6.873],[-8.369,13.819],[10.277,11.977],[-9.783,7.372],[13.677,11.919],[3.386,-6.308],[22.763,-16.59],[-16.315,-19.308]],"v":[[-106.962,-87.984],[-47.419,-51.608],[-108.351,-7.608],[-87.265,66.795],[-158.029,79.855],[-49.381,79.34],[-84.905,7.448],[-22.57,-56.8]],"c":true}]},{"t":140,"s":[{"i":[[13.068,-2.221],[-13.83,-13.369],[27.214,-15.812],[-0.311,-26.999],[25.003,1.193],[-17.437,5.94],[16.205,24.413],[-6.645,24.415]],"o":[[15.538,6.873],[-8.369,13.819],[10.277,11.977],[-9.783,7.372],[13.677,11.919],[3.386,-6.308],[20.906,-19.198],[-11.825,-11.123]],"v":[[-107.377,-85.519],[-62.992,-57.811],[-112.884,-10.198],[-91.417,63.154],[-158.029,79.855],[-64.424,81.943],[-86.646,6.343],[-38.362,-66.667]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.662745098039,0.282352941176,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.960784373564,0.694117647059,0.152941176471,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.53,"y":1},"o":{"x":0.48,"y":0},"t":0,"s":[{"i":[[10.059,-5.712],[-6.957,-15.634],[-4.077,-27.954],[-32.568,19.675],[-52.649,20.223],[6.028,9.215],[0.76,20.718],[19.478,1.367],[14.757,5.079],[5.914,-16.592]],"o":[[-6.465,29.934],[-8.941,15.384],[30.583,-1.483],[13.107,4.071],[-4.345,-42.79],[14.671,-14.276],[-29.722,-17.713],[-11.107,-20.963],[-30.999,15.569],[-20.067,-9.88]],"v":[[-303.549,-89.61],[-279.904,11.153],[-295.987,114.982],[-176.671,77.797],[-57.974,84.325],[-80.633,7.584],[-33.516,-69.842],[-111.47,-91.614],[-162.329,-168.094],[-230.17,-87.399]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.47,"y":0},"t":15,"s":[{"i":[[10.059,-5.712],[-6.957,-15.634],[-4.077,-27.954],[-32.568,19.675],[-52.649,20.223],[6.028,9.215],[0.76,20.718],[19.478,1.367],[14.757,5.079],[5.914,-16.592]],"o":[[-6.465,29.934],[-8.941,15.384],[30.583,-1.483],[13.107,4.071],[-10.498,-35.615],[14.671,-14.276],[-29.722,-17.713],[-5.368,-24.954],[-30.999,15.569],[-24.73,-13.174]],"v":[[-296.246,-97.526],[-279.904,11.153],[-287.697,102.165],[-176.671,77.797],[-47.039,73.569],[-80.633,7.584],[-26.594,-69.266],[-111.47,-91.614],[-152.699,-168.81],[-230.17,-87.399]],"c":true}]},{"i":{"x":0.53,"y":1},"o":{"x":0.167,"y":0.167},"t":22,"s":[{"i":[[11.312,6.325],[-7.995,-14.513],[-4.077,-27.954],[-32.568,19.675],[-52.358,21.471],[6.027,9.215],[-16.983,13.831],[19.39,-1.667],[14.757,5.079],[4.754,-17.926]],"o":[[-42.118,38.95],[-8.702,15.873],[30.583,-1.483],[13.107,4.071],[-10.935,-35.885],[12.184,-22.404],[-24.382,-33.228],[4.699,-26.758],[-60.966,-9.135],[-25.043,-15.544]],"v":[[-271.432,-117.463],[-279.904,11.153],[-288.121,105.365],[-176.671,77.797],[-37.322,81.782],[-80.313,6.159],[-11.44,-63.055],[-118.41,-92.688],[-113.65,-160.539],[-230.17,-87.399]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.47,"y":0},"t":54,"s":[{"i":[[9.11,-10.954],[-12.741,-9.391],[-4.077,-27.954],[-32.568,19.675],[-51.025,27.179],[6.028,9.215],[7.733,23.481],[18.988,-15.536],[14.757,5.079],[-0.546,-24.027]],"o":[[-0.455,30.434],[-7.607,18.107],[30.583,-1.483],[13.107,4.071],[-12.935,-37.119],[14.908,-19.432],[-28.4,-7.412],[-11.107,-20.963],[-34.462,19.642],[-31.804,-5.917]],"v":[[-320.175,-74.987],[-279.904,11.153],[-290.06,119.994],[-176.671,77.796],[-47.488,67.884],[-78.852,-0.358],[-48.364,-111.291],[-114.13,-92.808],[-184.28,-169.869],[-230.17,-87.399]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":71,"s":[{"i":[[22.977,-35.562],[-10.65,-11.647],[-4.077,-27.954],[-32.568,19.675],[-37.864,50.33],[13.537,0.781],[20.561,46.989],[19.024,-30.626],[35.045,-2.607],[-12.449,-28.89]],"o":[[26.696,25.681],[-8.089,17.123],[30.583,-1.483],[13.107,4.071],[-10.921,-32.875],[14.823,-17.569],[-26.38,-11.033],[-19.203,-46.466],[-8.398,23.732],[-27.563,-7.349]],"v":[[-344.541,-58.956],[-279.904,11.153],[-305.874,99.333],[-176.671,77.797],[-39.819,43.71],[-79.495,2.512],[-47.41,-113.364],[-113.168,-92.377],[-237.737,-147.776],[-229.166,-86.178]],"c":true}]},{"i":{"x":0.53,"y":1},"o":{"x":0.167,"y":0.167},"t":86,"s":[{"i":[[10.059,-5.712],[-6.957,-15.634],[-4.077,-27.954],[-32.568,19.675],[-52.649,20.223],[6.028,9.215],[0.76,20.718],[19.478,1.367],[14.757,5.079],[5.914,-16.592]],"o":[[-6.465,29.934],[-8.941,15.384],[30.583,-1.483],[13.107,4.071],[-4.345,-42.79],[14.671,-14.276],[-29.722,-17.713],[-11.107,-20.963],[-30.999,15.569],[-20.067,-9.88]],"v":[[-303.549,-89.61],[-279.904,11.153],[-295.987,114.982],[-176.671,77.797],[-57.974,84.325],[-80.633,7.584],[-33.516,-69.842],[-111.47,-91.614],[-162.329,-168.094],[-230.17,-87.399]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.47,"y":0},"t":93,"s":[{"i":[[10.059,-5.712],[-6.957,-15.634],[-4.077,-27.954],[-32.568,19.675],[-52.649,20.223],[6.028,9.215],[0.76,20.718],[19.478,1.367],[14.757,5.079],[5.914,-16.592]],"o":[[-6.465,29.934],[-8.941,15.384],[30.583,-1.483],[13.107,4.071],[-10.498,-35.615],[14.671,-14.276],[-29.722,-17.713],[-5.368,-24.954],[-30.999,15.569],[-24.73,-13.174]],"v":[[-296.246,-97.526],[-279.904,11.153],[-287.697,102.165],[-176.671,77.797],[-51.476,80.837],[-80.633,7.584],[-26.594,-69.266],[-111.47,-91.614],[-152.699,-168.81],[-230.17,-87.399]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":100,"s":[{"i":[[10.059,-5.712],[-6.957,-15.634],[-4.077,-27.954],[-32.568,19.675],[-50.781,14.002],[6.027,9.215],[0.76,20.718],[19.478,1.367],[13.634,12.022],[5.914,-16.592]],"o":[[-22.374,34.894],[-8.941,15.384],[30.583,-1.483],[13.107,4.071],[0.034,-37.77],[19.761,-11.037],[-23.537,-23.747],[-5.368,-24.954],[-42.465,6.721],[-14.829,-19.712]],"v":[[-276.531,-107.994],[-279.904,11.153],[-287.697,102.165],[-176.671,77.797],[-51.036,91.859],[-79.219,7.62],[-17.653,-59.139],[-112.486,-93.408],[-136.131,-166.625],[-230.17,-87.399]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103,"s":[{"i":[[10.059,-5.712],[-6.957,-15.634],[-4.077,-27.954],[-32.568,19.675],[-52.649,20.223],[6.028,9.215],[0.76,20.718],[19.478,1.367],[14.757,5.079],[5.914,-16.592]],"o":[[-6.465,29.934],[-8.941,15.384],[30.583,-1.483],[13.107,4.071],[-4.345,-42.79],[14.671,-14.276],[-29.722,-17.713],[-11.107,-20.963],[-30.999,15.569],[-20.067,-9.88]],"v":[[-303.549,-89.61],[-279.904,11.153],[-295.987,114.982],[-176.671,77.797],[-57.974,84.325],[-80.633,7.584],[-33.516,-69.842],[-111.47,-91.614],[-162.329,-168.094],[-230.17,-87.399]],"c":true}]},{"i":{"x":0.52,"y":1},"o":{"x":0.167,"y":0.167},"t":114,"s":[{"i":[[10.059,-5.712],[-6.957,-15.634],[-4.077,-27.954],[-32.568,19.675],[-52.649,20.223],[6.027,9.215],[0.76,20.718],[19.478,1.367],[14.757,5.079],[2.77,-22.277]],"o":[[-6.465,29.934],[-8.941,15.384],[30.583,-1.483],[13.107,4.071],[-4.345,-42.79],[14.671,-14.276],[-29.722,-17.713],[-11.107,-20.963],[-30.999,15.569],[-20.067,-9.88]],"v":[[-312.468,-82.35],[-279.904,11.153],[-301.596,107.299],[-176.671,77.797],[-54.322,72.971],[-80.633,7.584],[-41.264,-80.943],[-111.47,-91.614],[-179.959,-165.992],[-230.17,-87.399]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.48,"y":0},"t":127,"s":[{"i":[[10.059,-5.712],[-6.957,-15.634],[-4.077,-27.954],[-32.568,19.675],[-52.649,20.223],[6.028,9.215],[0.76,20.718],[19.478,1.367],[14.757,5.079],[5.914,-16.592]],"o":[[-6.465,29.934],[-8.941,15.384],[30.583,-1.483],[13.107,4.071],[-4.345,-42.79],[14.671,-14.276],[-29.722,-17.713],[-11.107,-20.963],[-30.999,15.569],[-20.067,-9.88]],"v":[[-303.549,-89.61],[-279.904,11.153],[-295.987,114.982],[-176.671,77.797],[-57.974,84.325],[-80.633,7.584],[-33.516,-69.842],[-111.47,-91.614],[-162.329,-168.094],[-230.17,-87.399]],"c":true}]},{"i":{"x":0.53,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[10.059,-5.712],[-6.957,-15.634],[-4.077,-27.954],[-32.568,19.675],[-52.649,20.223],[6.027,9.215],[0.76,20.718],[19.478,1.367],[13.634,12.022],[5.914,-16.592]],"o":[[-22.374,34.894],[-8.941,15.384],[30.583,-1.483],[13.107,4.071],[-10.498,-35.615],[19.761,-11.037],[-23.537,-23.747],[-5.368,-24.954],[-42.465,6.721],[-14.829,-19.712]],"v":[[-276.531,-107.994],[-279.904,11.153],[-287.697,102.165],[-176.671,77.797],[-42.251,80.056],[-79.219,7.62],[-17.653,-59.139],[-112.486,-93.408],[-136.131,-166.625],[-230.17,-87.399]],"c":true}]},{"t":140,"s":[{"i":[[10.059,-5.712],[-6.957,-15.634],[-4.077,-27.954],[-32.568,19.675],[-52.649,20.223],[6.028,9.215],[0.76,20.718],[19.478,1.367],[14.757,5.079],[5.914,-16.592]],"o":[[-6.465,29.934],[-8.941,15.384],[30.583,-1.483],[13.107,4.071],[-4.345,-42.79],[14.671,-14.276],[-29.722,-17.713],[-11.107,-20.963],[-30.999,15.569],[-20.067,-9.88]],"v":[[-303.549,-89.61],[-279.904,11.153],[-295.987,114.982],[-176.671,77.797],[-57.974,84.325],[-80.633,7.584],[-33.516,-69.842],[-111.47,-91.614],[-162.329,-168.094],[-230.17,-87.399]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.662745098039,0.282352941176,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.898039275525,0.400000029919,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0}]} \ No newline at end of file diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 0000000..cff15dc --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,43 @@ +@import "tailwindcss"; +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..4b7add2 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,120 @@ +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { ToastContainer } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; +import { io, Socket } from "socket.io-client"; +import { onIdTokenChanged } from "firebase/auth"; +import { useDispatch } from "react-redux"; + +import { auth } from "./firebase"; +import { useNotificationsSocket } from "./hooks/useNotificationsSocket"; +import { Router } from "./routers/Router"; +import { setUser, clearUser } from "./slices/user"; +import { getProfile } from "./api/requests/getProfile"; + +export const App = () => { + const { t } = useTranslation("common"); + const dispatch = useDispatch(); + const [socket, setSocket] = useState(null); + const [toastTheme, setToastTheme] = useState<"colored" | "dark">("colored"); + + useEffect(() => { + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); + const applyTheme = () => { + const savedTheme = localStorage.getItem("theme"); + const isDark = + savedTheme === "dark" || (!savedTheme && mediaQuery.matches); + document.documentElement.classList.toggle("dark", isDark); + setToastTheme(isDark ? "dark" : "colored"); + }; + + applyTheme(); + mediaQuery.addEventListener("change", applyTheme); + + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.attributeName === "class") { + const isDark = document.documentElement.classList.contains("dark"); + setToastTheme(isDark ? "dark" : "colored"); + } + }); + }); + + observer.observe(document.documentElement, { attributes: true }); + return () => { + mediaQuery.removeEventListener("change", applyTheme); + observer.disconnect(); + }; + }, []); + + useEffect(() => { + let currentSocket: Socket | null = null; + + const unsubscribeAuth = onIdTokenChanged(auth, async (user) => { + if (currentSocket) { + currentSocket.disconnect(); + setSocket(null); + } + + if (user) { + try { + const token = await user.getIdToken(); + const apiProfile = await getProfile(user); + + dispatch( + setUser({ + uid: user.uid, + email: user.email, + displayName: user.displayName, + photoURL: user.photoURL, + emailVerified: user.emailVerified, + isAnonymous: user.isAnonymous, + ...apiProfile, + }), + ); + + currentSocket = io(import.meta.env.VITE_SOCKETIO_SERVER_URL, { + auth: { token }, + reconnectionDelay: 5000, + }); + + setSocket(currentSocket); + + if (window.location.pathname.startsWith("/auth")) { + window.location.replace("/"); + } + } catch (error) { + console.error("Помилка синхронізації профілю:", error); + dispatch(clearUser()); + } + } else { + dispatch(clearUser()); + } + }); + + return () => { + unsubscribeAuth(); + if (currentSocket) currentSocket.disconnect(); + }; + }, [dispatch]); + + useNotificationsSocket(socket); + + return ( + <> + + + + ); +}; diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts new file mode 100644 index 0000000..7d934ff --- /dev/null +++ b/frontend/src/api/client.ts @@ -0,0 +1,6 @@ +import axios from "axios"; + +const apiClient = axios.create({ + baseURL: import.meta.env.VITE_BACKEND_URL, +}); +export default apiClient; diff --git a/frontend/src/api/queryClient.ts b/frontend/src/api/queryClient.ts new file mode 100644 index 0000000..8c52c1a --- /dev/null +++ b/frontend/src/api/queryClient.ts @@ -0,0 +1,11 @@ +import { QueryClient } from "@tanstack/react-query"; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, + refetchOnWindowFocus: false, + retry: 1, + }, + }, +}); diff --git a/frontend/src/api/requests/auth.ts b/frontend/src/api/requests/auth.ts new file mode 100644 index 0000000..3bdbc34 --- /dev/null +++ b/frontend/src/api/requests/auth.ts @@ -0,0 +1,8 @@ +import type { User } from "firebase/auth"; + +export const authHeaders = async (user: User) => { + const token = await user.getIdToken(); + return { + Authorization: `Bearer ${token}`, + }; +}; diff --git a/frontend/src/api/requests/createSubmission.ts b/frontend/src/api/requests/createSubmission.ts new file mode 100644 index 0000000..3663779 --- /dev/null +++ b/frontend/src/api/requests/createSubmission.ts @@ -0,0 +1,23 @@ +import apiClient from "../client"; + +export interface SubmissionUrl { + url_id: string; + value: string; +} + +export interface CreateSubmissionPayload { + team_id: number; + urls: SubmissionUrl[]; +} + +export const createSubmission = async ( + tournamentId: number, + taskId: number, + payload: CreateSubmissionPayload, +) => { + const response = await apiClient.post( + `/tournaments/${tournamentId}/tasks/${taskId}/submissions/`, + payload, + ); + return response.data; +}; diff --git a/frontend/src/api/requests/createTask.ts b/frontend/src/api/requests/createTask.ts new file mode 100644 index 0000000..54f2783 --- /dev/null +++ b/frontend/src/api/requests/createTask.ts @@ -0,0 +1,32 @@ +import type { User } from "firebase/auth"; +import apiClient from "../client"; +import { authHeaders } from "./auth"; + +export interface CriterionCreateData { + name: string; + description?: string | null; + weight?: number; + max_score?: number; +} + +export interface TaskCreateData { + title: string; + description?: string | null; + start_time: string; + end_time: string; + requirements: string[]; + criteria?: CriterionCreateData[]; +} + +export const createTask = async ( + tournamentId: number, + data: TaskCreateData, + user: User, +) => { + const resp = await apiClient.post( + `/tournaments/${tournamentId}/tasks/`, + data, + { headers: await authHeaders(user) }, + ); + return resp.data; +}; diff --git a/frontend/src/api/requests/createTeam.ts b/frontend/src/api/requests/createTeam.ts new file mode 100644 index 0000000..0b2915d --- /dev/null +++ b/frontend/src/api/requests/createTeam.ts @@ -0,0 +1,27 @@ +import apiClient from "../client"; + +export interface TeamMemberPayload { + full_name: string; + email: string; + telegram: string; + educational_institution: string; +} + +export interface CreateTeamPayload { + name: string; + team_email: string; + contact_info: string; + captain: TeamMemberPayload; + members: TeamMemberPayload[]; +} + +export const createTeam = async ( + tournamentId: number, + payload: CreateTeamPayload, +) => { + const response = await apiClient.post( + `/tournaments/${tournamentId}/teams/`, + payload, + ); + return response.data; +}; diff --git a/frontend/src/api/requests/createTournament.ts b/frontend/src/api/requests/createTournament.ts new file mode 100644 index 0000000..f6ede5b --- /dev/null +++ b/frontend/src/api/requests/createTournament.ts @@ -0,0 +1,36 @@ +import apiClient from "../client"; +import { getAuth } from "firebase/auth"; + +export interface TournamentData { + title: string; + description: string; + start_date: string; + reg_start: string; + reg_end: string; + min_people_in_team: number; + max_people_in_team: number; + max_teams: number; +} + +export const createTournament = async (data: TournamentData) => { + try { + const auth = getAuth(); + const user = auth.currentUser; + + if (!user) { + throw new Error("Користувач не авторизований"); + } + + const token = await user.getIdToken(); + + const resp = await apiClient.post("/tournaments/", data, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + return resp.data; + } catch (e) { + console.error(`Error occurred:`, e); + throw e; + } +}; \ No newline at end of file diff --git a/frontend/src/api/requests/deleteTask.ts b/frontend/src/api/requests/deleteTask.ts new file mode 100644 index 0000000..b9f7a9f --- /dev/null +++ b/frontend/src/api/requests/deleteTask.ts @@ -0,0 +1,14 @@ +import type { User } from "firebase/auth"; +import apiClient from "../client"; +import { authHeaders } from "./auth"; + +export const deleteTask = async ( + tournamentId: number, + taskId: number, + user: User, +) => { + await apiClient.delete( + `/tournaments/${tournamentId}/tasks/${taskId}/`, + { headers: await authHeaders(user) }, + ); +}; diff --git a/frontend/src/api/requests/deleteTournament.ts b/frontend/src/api/requests/deleteTournament.ts new file mode 100644 index 0000000..9043e6a --- /dev/null +++ b/frontend/src/api/requests/deleteTournament.ts @@ -0,0 +1,25 @@ +import { getAuth } from "firebase/auth"; +import apiClient from "../client"; + +export const deleteTournament = async (tournamentId: number) => { + try { + const auth = getAuth(); + const user = auth.currentUser; + + if (!user) { + throw new Error("Користувач не авторизований"); + } + + const token = await user.getIdToken(); + const response = await apiClient.delete(`/tournaments/${tournamentId}/`, { + headers: { + Authorization: `Bearer ${token}`, + } + }) + + return response.data; + } catch (e) { + console.error(`Error occurred: ${e}`); + throw e; + } +}; diff --git a/frontend/src/api/requests/deleteUser.ts b/frontend/src/api/requests/deleteUser.ts new file mode 100644 index 0000000..7cd7824 --- /dev/null +++ b/frontend/src/api/requests/deleteUser.ts @@ -0,0 +1,15 @@ +import type { User } from "firebase/auth"; +import apiClient from "../client"; + +export const deleteUser = async (user: User) => { + try { + const token = await user.getIdToken(); + await apiClient.delete("/profile/", { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + } catch (e) { + console.log(`Error occured ${e}`); + } +}; diff --git a/frontend/src/api/requests/finishEvaluation.ts b/frontend/src/api/requests/finishEvaluation.ts new file mode 100644 index 0000000..52ff889 --- /dev/null +++ b/frontend/src/api/requests/finishEvaluation.ts @@ -0,0 +1,18 @@ +import type { User } from "firebase/auth"; + +import apiClient from "../client"; +import { authHeaders } from "./auth"; +import type { JuryTask } from "./types"; + +export const finishEvaluation = async ( + tournamentId: number, + taskId: number, + user: User, +) => { + const resp = await apiClient.post( + `/tournaments/${tournamentId}/tasks/${taskId}/finish-evaluation/`, + {}, + { headers: await authHeaders(user) }, + ); + return resp.data; +}; diff --git a/frontend/src/api/requests/getAllTournaments.ts b/frontend/src/api/requests/getAllTournaments.ts new file mode 100644 index 0000000..4473942 --- /dev/null +++ b/frontend/src/api/requests/getAllTournaments.ts @@ -0,0 +1,11 @@ +import apiClient from "../client"; + +export const getAllTournaments = async () => { + try { + const resp = await apiClient.get("/tournaments/"); + return resp.data; + } catch (e) { + console.error(`Error occurred:`, e); + throw e; + } +}; diff --git a/frontend/src/api/requests/getAllUsers.ts b/frontend/src/api/requests/getAllUsers.ts new file mode 100644 index 0000000..852b87d --- /dev/null +++ b/frontend/src/api/requests/getAllUsers.ts @@ -0,0 +1,11 @@ +import apiClient from "../client"; + +export const getAllUsers = async () => { + try { + const resp = await apiClient.get("/users/"); + return resp.data; + } catch (e) { + console.error(`Error occurred:`, e); + throw e; + } +}; diff --git a/frontend/src/api/requests/getJuryAssignments.ts b/frontend/src/api/requests/getJuryAssignments.ts new file mode 100644 index 0000000..7cfc72d --- /dev/null +++ b/frontend/src/api/requests/getJuryAssignments.ts @@ -0,0 +1,18 @@ +import type { User } from "firebase/auth"; + +import apiClient from "../client"; +import { authHeaders } from "./auth"; +import type { JuryAssignment } from "./types"; + +export const getJuryAssignments = async ( + taskId: string | number, + user: User, +) => { + const resp = await apiClient.get( + `/jury/tasks/${taskId}/assignments/`, + { + headers: await authHeaders(user), + }, + ); + return resp.data; +}; diff --git a/frontend/src/api/requests/getJuryTasks.ts b/frontend/src/api/requests/getJuryTasks.ts new file mode 100644 index 0000000..a62534e --- /dev/null +++ b/frontend/src/api/requests/getJuryTasks.ts @@ -0,0 +1,12 @@ +import type { User } from "firebase/auth"; + +import apiClient from "../client"; +import { authHeaders } from "./auth"; +import type { JuryTask } from "./types"; + +export const getJuryTasks = async (user: User) => { + const resp = await apiClient.get("/jury/tasks/", { + headers: await authHeaders(user), + }); + return resp.data; +}; diff --git a/frontend/src/api/requests/getNews.ts b/frontend/src/api/requests/getNews.ts new file mode 100644 index 0000000..fd47dba --- /dev/null +++ b/frontend/src/api/requests/getNews.ts @@ -0,0 +1,76 @@ +import apiClient from "../client"; + +export interface NewsArticleBackend { + id: number; + title: string; + excerpt: string; + body: string; + is_important: boolean; + category_name: string; + created_at: string; + updated_at: string; + read_time: string; +} + +export interface NewsArticle { + id: string; + title: string; + excerpt: string; + content: string; + category: string; + categoryColor: string; + date: string; + readTime: string; +} + +const categoryStyles: Record< + string, + { display_name: string; categoryColor: string } +> = { + updates: { + display_name: "Оновлення", + categoryColor: "bg-purple-100 text-purple-700", + }, + volunteering: { + display_name: "Волонтерство", + categoryColor: "bg-green-100 text-green-700", + }, + tournaments: { + display_name: "Турніри", + categoryColor: "bg-blue-100 text-blue-700", + }, + community: { + display_name: "Спільнота", + categoryColor: "bg-orange-100 text-orange-700", + }, + education: { + display_name: "Освіта", + categoryColor: "bg-cyan-100 text-cyan-700", + }, + fanfic: { + display_name: "Фанфіки", + categoryColor: "bg-pink-100 text-pink-700", + }, +}; + +export const getNews = async (): Promise => { + const response = await apiClient.get("/news/"); + return response.data.map((item) => { + const category = + categoryStyles[item.category_name] || categoryStyles.updates; + return { + id: String(item.id), + title: item.title, + excerpt: item.excerpt, + content: item.body, + category: category.display_name, + categoryColor: category.categoryColor, + date: new Date(item.created_at).toLocaleDateString("uk-UA", { + day: "numeric", + month: "long", + year: "numeric", + }), + readTime: item.read_time, + }; + }); +}; diff --git a/frontend/src/api/requests/getProfile.ts b/frontend/src/api/requests/getProfile.ts new file mode 100644 index 0000000..f9ef851 --- /dev/null +++ b/frontend/src/api/requests/getProfile.ts @@ -0,0 +1,16 @@ +import type { User } from "firebase/auth"; +import apiClient from "../client"; + +export const getProfile = async (user: User) => { + try { + const token = await user.getIdToken(); + const resp = await apiClient.get("/profile/", { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + return resp.data; + } catch (e) { + console.log(`Error occured ${e}`); + } +}; diff --git a/frontend/src/api/requests/getRequirementOptions.ts b/frontend/src/api/requests/getRequirementOptions.ts new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/api/requests/getSubmissions.ts b/frontend/src/api/requests/getSubmissions.ts new file mode 100644 index 0000000..c8f03ee --- /dev/null +++ b/frontend/src/api/requests/getSubmissions.ts @@ -0,0 +1,49 @@ +import type { User } from "firebase/auth"; +import apiClient from "../client"; +import type { TournamentData } from "./createTournament"; +import type { ApiUserData } from "@/slices/user"; + +export interface TeamMember { + full_name: string; + email: string; + telegram: string; + educational_institution: string; +}; + +export interface Team { + name: string; + team_email: string; + contact_info: string; + tournament: TournamentData; + members: TeamMember[]; + captain: ApiUserData; +}; + +export interface SubmissionUrl { + url: string; +}; + +export interface SubmissionEvaluation { + jury: ApiUserData; +}; + +export interface Submission { + team: Team; + urls: SubmissionUrl[]; + evaluations: SubmissionEvaluation[]; +} + +export const getSubmissions = async (id: string | number, user: User) => { + try { + const token = await user.getIdToken(); + const resp = await apiClient.get(`/tournaments/${id}/submissions/`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + return resp.data; + } catch (e) { + console.log(`Error occured ${e}`); + } +}; diff --git a/frontend/src/api/requests/getTask.ts b/frontend/src/api/requests/getTask.ts new file mode 100644 index 0000000..ffd189f --- /dev/null +++ b/frontend/src/api/requests/getTask.ts @@ -0,0 +1,9 @@ +import apiClient from "../client"; +import type { JuryTask } from "./types"; + +export const getTask = async (tournamentId: number, taskId: number) => { + const resp = await apiClient.get( + `/tournaments/${tournamentId}/tasks/${taskId}/`, + ); + return resp.data; +}; diff --git a/frontend/src/api/requests/getTasks.ts b/frontend/src/api/requests/getTasks.ts new file mode 100644 index 0000000..7351d55 --- /dev/null +++ b/frontend/src/api/requests/getTasks.ts @@ -0,0 +1,11 @@ +import apiClient from "../client"; + +export const getTasks = async (tournamentId: number) => { + try { + const resp = await apiClient.get(`/tournaments/${tournamentId}/tasks/`); + return resp.data; + } catch (e) { + console.error(`Error occurred:`, e); + throw e; + } +}; diff --git a/frontend/src/api/requests/getTournamentLeaderboard.ts b/frontend/src/api/requests/getTournamentLeaderboard.ts new file mode 100644 index 0000000..bc27bfa --- /dev/null +++ b/frontend/src/api/requests/getTournamentLeaderboard.ts @@ -0,0 +1,19 @@ +import apiClient from "../client"; + +export interface LeaderboardEntry { + submission_id: number; + team_id: number; + team_name: string; + average_score: number; + total_score: number; + submitted_reviews: number; +} + +export const getTournamentLeaderboard = async ( + tournamentId: number, +): Promise => { + const response = await apiClient.get( + `/tournaments/${tournamentId}/leaderboard/`, + ); + return response.data; +}; diff --git a/frontend/src/api/requests/getUser.ts b/frontend/src/api/requests/getUser.ts new file mode 100644 index 0000000..1f51abe --- /dev/null +++ b/frontend/src/api/requests/getUser.ts @@ -0,0 +1,10 @@ +import apiClient from "../client"; + +export const getUser = async (uid: string) => { + try { + const resp = await apiClient.get(`/users/${uid}/`); + return resp.data; + } catch (e) { + console.log(`Error occured ${e}`); + } +}; diff --git a/frontend/src/api/requests/index.ts b/frontend/src/api/requests/index.ts new file mode 100644 index 0000000..bdb5c81 --- /dev/null +++ b/frontend/src/api/requests/index.ts @@ -0,0 +1,21 @@ +export * from "./createTask"; +export * from "./createTournament"; +export * from "./deleteTask"; +export * from "./deleteTournament"; +export * from "./deleteUser"; +export * from "./finishEvaluation"; +export * from "./getAllTournaments"; +export * from "./getAllUsers"; +export * from "./getJuryAssignments"; +export * from "./getJuryTasks"; +export * from "./getNews"; +export * from "./getProfile"; +export { getSubmissions } from "./getSubmissions"; +export * from "./getTask"; +export * from "./getUser"; +export * from "./requestRole"; +export * from "./saveAssignmentEvaluation"; +export * from "./types"; +export * from "./updateProfile"; +export * from "./updateTask"; +export * from "./updateTournament"; diff --git a/frontend/src/api/requests/requestRole.ts b/frontend/src/api/requests/requestRole.ts new file mode 100644 index 0000000..757ad71 --- /dev/null +++ b/frontend/src/api/requests/requestRole.ts @@ -0,0 +1,31 @@ +import apiClient from "../client"; +import type { User } from "firebase/auth"; + +interface RoleRequestInfo { + option_name: string; + value: string; +} + +export const requestRole = async ( + roleName: string, + user: User, + userId: number, + info: RoleRequestInfo[], +) => { + const token = await user.getIdToken(); + const response = await apiClient.post( + "/role-requests/", + { + role_name: roleName, + user_id: userId, + info, + }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); + + return response.status; +}; diff --git a/frontend/src/api/requests/requests.test.ts b/frontend/src/api/requests/requests.test.ts new file mode 100644 index 0000000..30fbe1a --- /dev/null +++ b/frontend/src/api/requests/requests.test.ts @@ -0,0 +1,146 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { getAuth } from "firebase/auth"; +import apiClient from "../client"; +import { getAllUsers } from "./getAllUsers"; +import { createTournament, type TournamentData } from "./createTournament"; +import { updateTask, type TaskUpdateData } from "./updateTask"; +import { requestRole } from "./requestRole"; + +vi.mock("../client", () => ({ + default: { + get: vi.fn(), + post: vi.fn(), + patch: vi.fn(), + delete: vi.fn(), + }, +})); + +vi.mock("firebase/auth", () => ({ + getAuth: vi.fn(), +})); + +type TokenUser = { + getIdToken: () => Promise; +}; + +describe("API request wrappers", () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(getAuth).mockReturnValue({ currentUser: null } as never); + }); + + describe("getAllUsers", () => { + it("returns response data on success", async () => { + const users = [{ id: 1 }, { id: 2 }]; + vi.mocked(apiClient.get).mockResolvedValue({ data: users }); + + await expect(getAllUsers()).resolves.toEqual(users); + expect(apiClient.get).toHaveBeenCalledWith("/users/"); + }); + + it("rethrows and logs when request fails", async () => { + const error = new Error("network failed"); + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + vi.mocked(apiClient.get).mockRejectedValue(error); + + await expect(getAllUsers()).rejects.toThrow("network failed"); + expect(errorSpy).toHaveBeenCalledWith("Error occurred:", error); + + errorSpy.mockRestore(); + }); + }); + + describe("createTournament", () => { + const payload: TournamentData = { + title: "Test", + description: "Desc", + start_date: "2026-06-01T00:00:00Z", + reg_start: "2026-05-01T00:00:00Z", + reg_end: "2026-05-20T00:00:00Z", + min_people_in_team: 2, + max_people_in_team: 4, + max_teams: 32, + }; + + it("sends authorized POST request and returns data", async () => { + const user: TokenUser = { + getIdToken: vi.fn().mockResolvedValue("token-123"), + }; + vi.mocked(getAuth).mockReturnValue({ currentUser: user } as never); + vi.mocked(apiClient.post).mockResolvedValue({ data: { id: 44 } }); + + await expect(createTournament(payload)).resolves.toEqual({ id: 44 }); + expect(apiClient.post).toHaveBeenCalledWith("/tournaments/", payload, { + headers: { Authorization: "Bearer token-123" }, + }); + }); + + it("throws when user is not authenticated", async () => { + vi.mocked(getAuth).mockReturnValue({ currentUser: null } as never); + + await expect(createTournament(payload)).rejects.toThrow( + "Користувач не авторизований", + ); + expect(apiClient.post).not.toHaveBeenCalled(); + }); + }); + + describe("updateTask", () => { + const taskPayload: TaskUpdateData = { + title: "New title", + requirements: ["README"], + }; + + it("sends authorized PATCH request and returns data", async () => { + const user: TokenUser = { + getIdToken: vi.fn().mockResolvedValue("task-token"), + }; + vi.mocked(getAuth).mockReturnValue({ currentUser: user } as never); + vi.mocked(apiClient.patch).mockResolvedValue({ data: { ok: true } }); + + await expect(updateTask(10, 99, taskPayload)).resolves.toEqual({ ok: true }); + expect(apiClient.patch).toHaveBeenCalledWith( + "/tournaments/10/tasks/99/", + taskPayload, + { + headers: { Authorization: "Bearer task-token" }, + }, + ); + }); + + it("throws when current user is missing", async () => { + vi.mocked(getAuth).mockReturnValue({ currentUser: null } as never); + + await expect(updateTask(1, 2, {})).rejects.toThrow( + "Користувач не авторизований", + ); + expect(apiClient.patch).not.toHaveBeenCalled(); + }); + }); + + describe("requestRole", () => { + it("posts role request with bearer token and returns status", async () => { + const user = { + getIdToken: vi.fn().mockResolvedValue("role-token"), + }; + vi.mocked(apiClient.post).mockResolvedValue({ status: 201 }); + + const status = await requestRole("organizer", user as never, 100, [ + { option_name: "github", value: "anna-dev" }, + ]); + + expect(status).toBe(201); + expect(apiClient.post).toHaveBeenCalledWith( + "/role-requests/", + { + role_name: "organizer", + user_id: 100, + info: [{ option_name: "github", value: "anna-dev" }], + }, + { + headers: { Authorization: "Bearer role-token" }, + }, + ); + }); + }); +}); diff --git a/frontend/src/api/requests/saveAssignmentEvaluation.ts b/frontend/src/api/requests/saveAssignmentEvaluation.ts new file mode 100644 index 0000000..f288708 --- /dev/null +++ b/frontend/src/api/requests/saveAssignmentEvaluation.ts @@ -0,0 +1,28 @@ +import type { User } from "firebase/auth"; + +import apiClient from "../client"; +import { authHeaders } from "./auth"; +import type { CriterionScore, SubmissionEvaluation } from "./types"; + +export const saveAssignmentEvaluation = async ( + assignmentId: number, + data: { comment?: string; criterion_scores: CriterionScore[] }, + user: User, + hasExistingEvaluation: boolean, +) => { + const config = { + headers: await authHeaders(user), + }; + const resp = hasExistingEvaluation + ? await apiClient.patch( + `/jury/assignments/${assignmentId}/evaluation/`, + data, + config, + ) + : await apiClient.post( + `/jury/assignments/${assignmentId}/evaluation/`, + data, + config, + ); + return resp.data; +}; diff --git a/frontend/src/api/requests/types.ts b/frontend/src/api/requests/types.ts new file mode 100644 index 0000000..ffa0afd --- /dev/null +++ b/frontend/src/api/requests/types.ts @@ -0,0 +1,104 @@ +// TODO: Move the types to src/types +export interface TaskStatusOption { + name: string; + display_name: string; +} + +export interface TournamentStatusOption { + name: string; + display_name: string; +} + +export interface AppConfig { + task_statuses: TaskStatusOption[]; + tournament_statuses: TournamentStatusOption[]; +} + +export interface TeamMember { + id: number; + full_name: string; + email: string; + telegram: string; + educational_institution?: string | null; +} + +export interface TournamentSummary { + id: number; + title: string; + description: string; +} + +export interface Team { + id: number; + name: string; + team_email: string; + contact_info: string; + tournament: TournamentSummary; + members: TeamMember[]; +} + +export interface SubmissionUrl { + id: number; + url_id: string; + value: string; + url: { + name: string; + display_name: string; + }; +} + +export interface EvaluationCriterion { + id: number; + name: string; + description?: string | null; + weight: number; + max_score: number; +} + +export interface JuryTask { + id: number; + title: string; + description?: string | null; + status_id: string; + start_time: string; + end_time: string; + min_reviews_per_submission: number; + max_score: number; + is_leaderboard_visible: boolean; + criteria: EvaluationCriterion[]; +} + +export interface CriterionScore { + id?: number; + criterion_id: number; + score: number; +} + +export interface SubmissionEvaluation { + id: number; + assignment_id: number; + submission_id: number; + jury_id: number; + comment?: string | null; + created_at?: string | null; + criterion_scores: CriterionScore[]; +} + +export interface Submission { + id: number; + team_id: number; + task_id: number; + team: Team; + urls: SubmissionUrl[]; +} + +export interface JuryAssignment { + id: number; + task_id: number; + submission_id: number; + jury_id: number; + status: string; + submission: Submission; + task: JuryTask; + evaluation?: SubmissionEvaluation | null; +} diff --git a/frontend/src/api/requests/updateProfile.ts b/frontend/src/api/requests/updateProfile.ts new file mode 100644 index 0000000..af16045 --- /dev/null +++ b/frontend/src/api/requests/updateProfile.ts @@ -0,0 +1,25 @@ +import type { User } from "firebase/auth"; +import apiClient from "../client"; + +interface UpdateUserData { + full_name?: string; + telegram?: string; + github?: string; + discord?: string; +} + +export const updateProfile = async (user: User, data: UpdateUserData) => { + try { + const token = await user.getIdToken(); + const response = await apiClient.patch(`/profile/`, data, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + return response.data; + } catch (e) { + console.error(`Error occurred: ${e}`); + throw e; + } +}; diff --git a/frontend/src/api/requests/updateTask.ts b/frontend/src/api/requests/updateTask.ts new file mode 100644 index 0000000..b1e6d24 --- /dev/null +++ b/frontend/src/api/requests/updateTask.ts @@ -0,0 +1,27 @@ +import type { User } from "firebase/auth"; +import apiClient from "../client"; +import { authHeaders } from "./auth"; +import type { CriterionCreateData } from "./createTask"; + +export interface TaskUpdateData { + title?: string; + description?: string | null; + start_time?: string; + end_time?: string; + requirements?: string[]; + criteria?: CriterionCreateData[]; +} + +export const updateTask = async ( + tournamentId: number, + taskId: number, + data: TaskUpdateData, + user: User, +) => { + const resp = await apiClient.patch( + `/tournaments/${tournamentId}/tasks/${taskId}/`, + data, + { headers: await authHeaders(user) }, + ); + return resp.data; +}; diff --git a/frontend/src/api/requests/updateTournament.ts b/frontend/src/api/requests/updateTournament.ts new file mode 100644 index 0000000..9a1cfc6 --- /dev/null +++ b/frontend/src/api/requests/updateTournament.ts @@ -0,0 +1,39 @@ +import apiClient from "../client"; +import { getAuth } from "firebase/auth"; + +interface UpdateTournamentData { + title?: string; + description?: string; + start_date?: string; + reg_start?: string; + reg_end?: string; + max_teams?: number; +} + +export const updateTournament = async ( + tournamentId: number, + data: UpdateTournamentData, +) => { + try { + + const auth = getAuth(); + const user = auth.currentUser; + + if (!user) { + throw new Error("Користувач не авторизований"); + } + + const token = await user.getIdToken(); + + const response = await apiClient.patch(`/tournaments/${tournamentId}/`,data,{ + headers: { + Authorization: `Bearer ${token}` + } + }); + + return response.data; + } catch (e) { + console.error(`Error occurred: ${e}`); + throw e; + } +}; diff --git a/frontend/src/components/Footer.test.tsx b/frontend/src/components/Footer.test.tsx new file mode 100644 index 0000000..06521c3 --- /dev/null +++ b/frontend/src/components/Footer.test.tsx @@ -0,0 +1,91 @@ +import { describe, expect, it } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { MemoryRouter } from "react-router-dom"; +import { Footer } from "./Footer"; + +const renderFooter = () => + render( + +